๊ฐœ์š”

๋Œ€์šฉ๋Ÿ‰์„œ๋น„์Šค๋ฅผ ํ•˜๋ฉด ๋™์‹œ์„ฑ ์ฒ˜๋ฆฌ์— ๋Œ€ํ•œ ๊ณ ๋ฏผ์„ ๋งŽ์ด ํ•˜๊ฒŒ ๋œ๋‹ค. ๋™์‹œ์— ๋“ค์–ด์˜ค๋Š” ์š”์ฒญ์— ๋Œ€ํ•ด์„œ ๋ˆ„๋ฝ์—†์ด ์ œ๋Œ€๋กœ ๋ฐ˜์˜์ด ๋˜๋Š”๊ฐ€์— ๋Œ€ํ•œ ๋‚ด์šฉ๋“ค.

 

๋น„์Šทํ•œ ๊ธ€๋“ค์ด ๋งŽ์•„์„œ ๋‚˜ ๋˜ํ•œ ๊ทธ๋Ÿฐ ๊ธ€์„ ์“ธ ํ•„์š”๋Š” ์—†์„ ๊ฒƒ ๊ฐ™๊ณ , ๋‹จ์ง€ mysql ๊ณตํ™ˆ์— ์žˆ๋Š” Locking Reads ๋‚ด์šฉ์„ ์ฝ์–ด๋ณด๊ณ , InnoDB engine ์—์„œ ์ œ๊ณตํ•˜๋Š” ๋ฝ์ธ Shared Lock ๊ณผ Exclusive Lock ์„ ์ดํ•ดํ•˜๋Š” ์‹œ๊ฐ„์„ ๊ฐœ์ธ์ ์œผ๋กœ ๊ฐ€์กŒ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜๋‹จ์—์„œ ์–ด๋–ป๊ฒŒ ์ ์šฉํ• ์ง€์— ๋Œ€ํ•œ ์ฝ”๋“œ๋„ ์ž‘์„ฑํ–ˆ๋‹ค. (์‚ฌ์‹ค ์ด ๋‚ด์šฉ๋„ ๋ธ”๋กœ๊ทธ์— ๋ฌด์ˆ˜ํžˆ ๋งŽ์ง€๋งŒ ๋‚ด ๊ฐœ๋…์ •๋ฆฌ์ฐจ ์ž‘์„ฑํ•œ๋‹ค.)

 

๊ณตํ™ˆ์˜ ๋‚ด์šฉ์€ ๊ตฌ๊ธ€๋ฒˆ์—ญ๊ธฐ์— ๋„์›€์„ ์–ป์—ˆ๋‹ค. (+ ์˜์—ญ)

 

 

Locking Reads (์ฝ๊ธฐ์ž ๊ธˆ)

์ €์žฅ์ด๋‚˜ ์ˆ˜์ •์— ๋Œ€ํ•œ ํŠธ๋žœ์žญ์…˜ ์ˆ˜ํ–‰ ์‹œ, ์ผ๋ฐ˜์ ์ธ ์กฐํšŒ ๊ตฌ๋ฌธ์€ ๋ณดํ˜ธ๋ฐ›์„ ์ˆ˜ ์—†๋‹ค. ํƒ€ ํŠธ๋žœ์žญ์…˜์ด ๋งŒ์•ฝ ํŠน์ • ๋กœ์šฐ์— ๋Œ€ํ•ด์„œ ์ˆ˜์ •/์ €์žฅ์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. InnoDB ๋Š” ํ•ด๋‹น ์‚ฌํ•ญ์— ๋Œ€ํ•œ ๋ณดํ˜ธ์žฅ์น˜๋ฅผ ์œ„ํ•ด ๋‘ ๊ฐ€์ง€ ํƒ€์ž…์˜ Locking Reads ๋ฅผ ์ œ๊ณตํ•˜๊ณ  ์žˆ๋‹ค. ์ถ”๊ฐ€๋กœ Locking Reads ๋ฅผ ์ด์šฉํ• ๋•Œ๋Š” autocommit ์ด disabled ๋กœ ์ฒ˜๋ฆฌ๋˜์–ด์žˆ์–ด์•ผ ํ•œ๋‹ค. (autocommit = 0)

 

 

SELECT ... FOR SHARE : Shared Lock (S-Lock : ๊ณต์œ ์ž ๊ธˆ)

์ฝ์„๋งŒํ•œ ํŠน์ • ๋กœ์šฐ์— ๋Œ€ํ•ด์„œ shared mode lock ์„ ์„ธํŒ…ํ•  ์ˆ˜ ์žˆ๋‹ค. ๋‹ค๋ฅธ ์„ธ์…˜๋„ ํ•ด๋‹น ๋กœ์šฐ๋ฅผ ์ฝ์„ ์ˆ˜ ์žˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ๋จผ์ € ์ฝ์€ transaction ์ด commit ํ•˜๊ธฐ ์ด์ „์— ๋‹ค๋ฅธ ์„ธ์…˜์—์„œ ํ•ด๋‹น ๋กœ์šฐ์˜ ๋ฐ์ดํ„ฐ๋ฅผ update ํ•  ์ˆ˜ ์—†๋‹ค. ๋‹ค๋ฅธ ์„ธ์…˜์˜ ๋ณ€๊ฒฝ์ฟผ๋ฆฌ๋Š” ๊ธฐ์กด์˜ transaction ์ด commit ์„ ์ˆ˜ํ–‰ํ•˜๊ธฐ ์ด์ „๊นŒ์ง€ ๊ณ„์† ๊ธฐ๋‹ค๋ฆฌ๋‹ค๊ฐ€ commit ์ด ๋˜๋ฉด ๊ทธ์ œ์„œ์•ผ update ์ฟผ๋ฆฌ๊ฐ€ ์ˆ˜ํ–‰๋œ๋‹ค. SELECT ... FOR SHARE ๋กœ ํŠน์ •๋กœ์šฐ์— ๋ฝ์ด ๊ฑธ๋ ค์žˆ๋”๋ผ๋„ ํƒ€ ํŠธ๋žœ์žญ์…˜๋„ ๊ทธ ๋กœ์šฐ์— ๋ฝ์„ ๊ฑธ ์ˆ˜ ์žˆ๋‹ค.

 

 

SELECT ... FOR UPDATE : Exclusive Lock (X-Lock : ๋ฐฐํƒ€์ž ๊ธˆ)

ํŠน์ • ๋กœ์šฐ์— update ๋ฌธ์„ ์‹คํ–‰ํ•œ๊ฒƒ์ฒ˜๋Ÿผ lock ์ด ์„ธํŒ…๋œ๋‹ค. ๋‹ค๋ฅธ transaction ์€ ํ•ด๋‹น ๋กœ์šฐ๋ฅผ ์—…๋ฐ์ดํŠธํ•  ์ˆ˜ ์—†์ด blocked ์ด ๋œ๋‹ค. ๋‹ค๋ฅธ transaction ์ด SELECT ... FOM SHARE ๋ฅผ ํ•˜๋”๋ผ๋„ blocked ๋œ๋‹ค. X-Lock ์ด ๊ฑธ๋ฆฌ๋ฉด ํƒ€ ํŠธ๋žœ์žญ์…˜์€ ๋ฝ์„ ๊ฑธ ์ˆ˜ ์—†๋‹ค.

 

 

S-Lock & X-Lock ๋‹ค์ด์–ด๊ทธ๋žจ

์‹ค์ œ ํ„ฐ๋ฏธ๋„์„ 2๊ฐœ์ •๋„ ๋„์–ด๋†“๊ณ , ์ฟผ๋ฆฌ๋‚ ๋ฆฌ๋ฉด์„œ ํ…Œ์ŠคํŠธํ•ด๋ณธ ๊ฒฐ๊ณผ๋ฅผ ๋‹ค์ด์–ด๊ทธ๋žจ์œผ๋กœ ์˜ฎ๊ฒจ๋ดค๋‹ค.

 

 

๊ทธ๋ž˜์„œ..?

๋‹จ์ˆœ ๋™์‹œ์— ์ฝ๋Š” ํšŸ์ˆ˜๊ฐ€ ๋งŽ๋‹ค๋ฉด, SELECT ... FOR SHARE ๋ฅผ ํ•ด๋„ ์ƒ๊ด€์—†๋‹ค.

ํ•˜์ง€๋งŒ ๋™์‹œ์ฝ๊ธฐํšŸ์ˆ˜ < ๋™์‹œ์“ฐ๊ธฐํšŸ์ˆ˜์ธ ๊ฒฝ์šฐ์—๋Š” SELECT ... FROM UPDATE ๋กœ ๋ฐฐํƒ€์ž ๊ธˆ์„ ๊ฑธ์–ด๋‘ฌ์•ผํ•œ๋‹ค.

 

 

์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋ ˆ๋ฒจ๋‹จ ์ฝ”๋“œ

@Transactional
fun addItemByInventoryIdWithPsRead(id: Long): Inventory {
	// s-lock
    // ๋™์‹œ์— ์š”์ฒญ ์˜ค๊ฒŒ๋œ๋‹ค๋ฉด, ๋ฐ๋“œ๋ฝ์ด ๋ฐœ์ƒํ–ˆ๋‹ค๋ฉด์„œ ์š”์ฒญ์ด ์‹คํŒจํ•œ๋‹ค.
    val inventory = entityManager.find(InventoryV3::class.java, id, LockModeType.PESSIMISTIC_READ)
        ?: throw RuntimeException("์ธ๋ฒคํ† ๋ฆฌ ๋ฏธํ™•์ธ : $id")

    inventory.addItemIfPossibleOrThrow()
    return inventory
}
@Transactional
fun addItemByInventoryIdWithPsWrite(id: Long): Inventory {
    // x-lock timeout ์„ค์ •
    // ๋™์‹œ์— ์š”์ฒญ์ด ์˜ฌ ๋•Œ ๋ฐ๋“œ๋ฝ์„ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด ํƒ€์ž„์•„์›ƒ์„ ๊ฑธ์–ด๋‘”๋‹ค.
    // ๋งŒ์•ฝ ํƒ€์ž„์•„์›ƒ์„ ๋„˜์–ด๊ฐ€๋ฉด 'LockTimeoutException' ์ด ๋ฐœ์ƒํ•œ๋‹ค.
    val properties: Map<String, Any> = mutableMapOf<String, Long>().apply {
        this["javax.persistence.lock.timeout"] = 1000L
    }
    val inventory = entityManager.find(Inventory::class.java, id, LockModeType.PESSIMISTIC_WRITE, properties)
        ?: throw RuntimeException("์ธ๋ฒคํ† ๋ฆฌ ๋ฏธํ™•์ธ : $id")

    inventory.addItemIfPossibleOrThrow()
    return inventory
}

 

(์ˆ˜์ • : 2022-11-12)

MySQL & JPA ๋กœ๋Š” ๋ฐฐํƒ€์ž ๊ธˆ์—์„œ javax.persisntence.lock.timeout ์ด ์ง€์›๋˜์ง€ ์•Š๋Š”๋‹ค. lock.timeout ๋Œ€์‹ ์— javax.persistence.query.timeout ์„ ์“ฐ๋Š” ๊ฑธ ๊ณ ๋ คํ•œ๋‹ค. lock.timeout ์„ ์“ฐ๊ณ ์ž ํ•œ๋‹ค๋ฉด, ์ฝ”๋“œ๋ ˆ๋ฒจ์—์„œ ๋ณ„๋„ mysql ๋‹จ์œผ๋กœ native query ๋ฅผ ๊ฐœ๋ณ„ ํŠธ๋žœ์žญ์…˜๋งˆ๋‹ค ์ฟผ๋ฆฌ ํ˜ธ์ถœ์ „์— ๋‚ ๋ ค์ฃผ์–ด์•ผ ํ•œ๋‹ค.

(https://github.com/andistoev/testing-pessimistic-locking-handling-spring-boot-jpa-mysql/blob/master/src/main/java/com/andistoev/psmlockingservice/CustomizedItemRepositoryImpl.java)

 

์ฐธ๊ณ 

https://dev.mysql.com/doc/refman/8.0/en/innodb-locking-reads.html

https://dev.mysql.com/doc/refman/8.0/en/innodb-deadlock-example.html

Posted by doubler
,