[Lock] DB Lock & JPA Lock ์ฝ”๋“œ๋กœ ์‚ดํŽด๋ณด์ž (MySQL)

DataBase๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ์˜์†์ ์œผ๋กœ ์ €์žฅํ•˜๊ณ  ์žˆ๋Š” ์‹œ์Šคํ…œ์ด๋‹ค. ์ด๋Ÿฐ ์‹œ์Šคํ…œ์€ ๊ฐ™์€ ์ž์›(๋ฐ์ดํ„ฐ)์— ๋Œ€ํ•ด์„œ ๋™์‹œ์— ์ ‘๊ทผํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์ƒ๊ธธ ์ˆ˜ ๋ฐ–์— ์—†๋‹ค. ์ด๋Ÿด ๊ฒฝ์šฐ ๋ฐ์ดํ„ฐ๊ฐ€ ์˜ค์—ผ ๋  ์ˆ˜ ์žˆ๋Š”๋ฐ ๊ทธ๋ ‡๊ฒŒ ๋˜์ง€ ์•Š๋„๋ก ๋ฐ์ดํ„ฐ์˜ ์ผ๊ด€์„ฑ๊ณผ ๋ฌด๊ฒฐ์„ฑ์„ ์œ ์ง€ํ•ด์•ผํ•  ํ•„์š”๊ฐ€ ์žˆ๋‹ค.

 

์˜ˆ๋ฅผ ๋“ค์–ด ์ˆ˜๊ฐ•์‹ ์ฒญ ์‹œ์Šคํ…œ์—์„œ 1๋ช…๋งŒ์ด ์ •์›์œผ๋กœ ๋‚จ๊ฒŒ๋˜์—ˆ๋‹ค. ์—ฌ๊ธฐ์„œ 2์‚ฌ๋žŒ์ด ๊ฑฐ์˜ ๋™์‹œ์— ๋ฒ„ํŠผ์„ ๋ˆŒ๋ €๋‹ค. ์„ฑ๊ณต์€ 1๋ช…๋งŒ ๋˜์•ผํ•œ๋‹ค. ์ด๋Ÿฐ ์ƒํ™ฉ์—์„œ DBMS(DataBase Management System)๊ฐ€ ์‚ฌ์šฉํ•˜๋Š” ๊ณตํ†ต์ ์ธ ๋ฐฉ๋ฒ•์ด Lock์ด๋ผ๋Š” ๊ฒƒ๋‹ค.

 

1. Lock ์ด๋ž€?

Lock์ด๋ž€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ๋™์‹œ์„ฑ๊ณผ ๋ฐ์ดํ„ฐ ์ผ๊ด€์„ฑ์„ ๋ณด์žฅํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ๋˜๋Š” ๋ฉ”์ปค๋‹ˆ์ฆ˜

ํ˜น์€ ํŠธ๋žœ์žญ์…˜ ์ฒ˜๋ฆฌ์˜ ์ˆœ์ฐจ์„ฑ์„ ๋ณด์žฅํ•˜๊ธฐ ์œ„ํ•œ ๋ฐฉ๋ฒ•

 

 

์—ฌ๋Ÿฌ ์ปค๋„ฅ์…˜์ด ํ•˜๋‚˜์˜ Data์— ๋Œ€ํ•ด ์ ‘๊ทผํ•˜๋ ค๊ณ  ํ•  ๋•Œ ๋ฐ์ดํ„ฐ์˜ ์ผ๊ด€์„ฑ์ด ๋ณด์žฅ๋˜์ง€ ๋ชป ํ•˜๋Š” ๋ฌธ์ œ๊ฐ€ ์žˆ๋‹ค. 

 

์ด๋•Œ ํ•˜๋‚˜์˜ ์ปค๋„ฅ์…˜A๊ฐ€ Data์— ์ ‘๊ทผํ•˜๋ฉด์„œ Lock์„ ๊ฑธ์–ด๋ฒ„๋ฆฌ๋ฉด ๋‹ค๋ฅธ ์ปค๋„ฅ์…˜(B,C,D)๋Š” Data์— ์ ‘๊ทผํ•  ์ˆ˜ ์—†๊ฒŒ ๋œ๋‹ค.

 

์ด๋ ‡๊ฒŒ ์ž๋ฌผ์‡ ๋ฅผ ๊ฑธ๊ณ  ํ‘ธ๋Š” ํ–‰์œ„๋ฅผ Lock์ด๋ผ๊ณ  ํ•œ๋‹ค. 

 

 

2. Lock๊ณผ Transaction ?

Lock๊ณผ Transaction์€ ๊ฐ™์€ ์—ญํ• ์„ ํ•˜๋Š”๊ฑธ๊นŒ??

 

Lock์€ ๋™์‹œ์— ๋ฐœ์ƒํ•˜๋Š” ์ˆ˜์ • ์š”์ฒญ์— ๋Œ€ํ•œ ๋ฐ์ดํ„ฐ ์ผ๊ด€์„ฑ์„ ์ง€ํ‚ค๊ธฐ ์œ„ํ•œ ๋ฉ”์ปค๋‹ˆ์ฆ˜ ์ค‘ ํ•˜๋‚˜์ด๋‹ค. 

 

ํŠธ๋žœ์žญ์…˜์€ ์—ฌ๋Ÿฌ ํŠธ๋žœ์žญ์…˜์— ๋Œ€ํ•ด ๊ฐ ํŠธ๋žœ์žญ์…˜์„ ์–ด๋–ป๊ฒŒ ์ฒ˜๋ฆฌํ• ์ง€์— ๋Œ€ํ•œ ์ „๋žต์ด๋‹ค. 

 

์ •๋ฆฌํ•˜๋ฉด ํŠธ๋žœ์žญ์…˜๋“ค์„ ์–ด๋–ป๊ฒŒ ์ฒ˜๋ฆฌํ• ์ง€์— ๋Œ€ํ•œ ์ „๋žต ์ค‘ ๊ตฌํ˜„ ๋ฐฉ๋ฒ• ์ค‘ ํ•˜๋‚˜๊ฐ€ Lock์ธ ๊ฒƒ์ด๋‹ค. 

์ฆ‰, ํŠธ๋žœ์žญ์…˜์˜ ๊ฒฉ๋ฆฌ์ˆ˜์ค€์„ ๊ตฌํ˜„ํ•  ๋ฐฉ๋ฒ• ์ค‘ ํ•˜๋‚˜๊ฐ€ Lock์ด๋‹ค. 

 

์ด ๋‘˜์˜ ๊ด€๊ณ„๋Š” ์ด์ฒ˜๋Ÿผ ๋‚˜ํƒ€๋‚ผ ์ˆ˜ ์žˆ๋‹ค. 



3. Lock ์ „๋žต

- ๋‚™๊ด€์  Lock

ํŠธ๋žœ์žญ์…˜์ด ์• ์ดˆ์— ์ถฉ๋Œ์ด ๋ฐœ์ƒํ•˜์ง€ ์•Š๋Š”๋‹ค ๋ผ๊ณ  ๊ฐ€์ •ํ•˜๊ณ  ์‚ฌ์šฉํ•˜๋Š” ์ „๋žต

 

๋‚ด๊ฐ€ ๋จผ์ € ์ด ๊ฐ’์„ ์ˆ˜์ •ํ–ˆ๋‹ค๊ณ  ๋ช…์‹œํ•˜์—ฌ ๋‹ค๋ฅธ ์‚ฌ๋žŒ์ด ๋™์ผํ•œ ์กฐ๊ฑด์œผ๋กœ ๊ฐ’์„ ์ˆ˜์ •ํ•  ์ˆ˜ ์—†๊ฒŒ ํ•˜๋Š” ๊ฒƒ์ด๋‹ค. ๊ทธ๋Ÿฐ๋ฐ ์ž˜ ๋ณด๋ฉด ์ด ํŠน์ง•์€ DB์—์„œ ์ œ๊ณตํ•ด์ฃผ๋Š” ํŠน์ง•์„ ์ด์šฉํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹Œ Application Level์—์„œ ์žก์•„์ฃผ๋Š” Lock์ด๋‹ค. 

 

 

 

- ๋น„๊ด€์  Lock

ํŠธ๋žœ์žญ์…˜์ด ๋งค๋ฒˆ ์ถฉ๋Œ์ด ๋ฐœ์ƒํ•œ๋‹ค ๋ผ๊ณ  ๊ฐ€์ •ํ•˜๊ณ  ์‚ฌ์šฉํ•˜๋Š” ์ „๋žต

 

๋น„๊ด€์  ๋ฝ์ด๋ž€ ํŠธ๋žœ์žญ์…˜์ด ์‹œ์ž‘๋  ๋•Œ Shared Lock ๋˜๋Š” Exclusive Lock์„ ๊ฑธ๊ณ  ์‹œ์ž‘ํ•˜๋Š” ๋ฐฉ๋ฒ•์ด๋‹ค.

 

 

Shared Lock์„ ๊ฑธ๊ฒŒ ๋˜๋ฉด write๋ฅผ ํ•˜๊ธฐ์œ„ํ•ด์„œ๋Š” Exclucive Lock์„ ์–ป์–ด์•ผํ•˜๋Š”๋ฐ Shared Lock์ด ๋‹ค๋ฅธ ํŠธ๋žœ์žญ์…˜์— ์˜ํ•ด์„œ ๊ฑธ๋ ค ์žˆ์œผ๋ฉด ํ•ด๋‹น Lock์„ ์–ป์ง€ ๋ชปํ•ด์„œ ์—…๋ฐ์ดํŠธ๋ฅผ ํ•  ์ˆ˜ ์—†๋‹ค. ์ˆ˜์ •์„ ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ํ•ด๋‹น ํŠธ๋žœ์žญ์…˜์„ ์ œ์™ธํ•œ ๋ชจ๋“  ํŠธ๋žœ์žญ์…˜์ด ์ข…๋ฃŒ(commit) ๋˜์–ด์•ผํ•œ๋‹ค.

 

 

์œ„์˜ ๋„์‹๋„๋ฅผ ๋ณด๋ฉด์„œ ๋น„๊ด€์  ๋ฝ์— ๋Œ€ํ•ด์„œ ์ œ๋Œ€๋กœ ์ดํ•ดํ•ด๋ณด์ž. 

  1. Transaction_1 ์—์„œ table์˜ Id 2๋ฒˆ์„ ์ฝ์Œ ( name = Karol )
  2. Transaction_2 ์—์„œ table์˜ Id 2๋ฒˆ์„ ์ฝ์Œ ( name = Karol )
  3. Transaction_2 ์—์„œ table์˜ Id 2๋ฒˆ์˜ name์„ Karol2๋กœ ๋ณ€๊ฒฝ ์š”์ฒญ ( name = Karol )
    • ํ•˜์ง€๋งŒ Transaction 1์—์„œ ์ด๋ฏธ shared Lock์„ ์žก๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์— Blocking
  4. Transaction_1 ์—์„œ ํŠธ๋žœ์žญ์…˜ ํ•ด์ œ (commit)
  5. Blocking ๋˜์–ด์žˆ์—ˆ๋˜ Transaction_2์˜ update ์š”์ฒญ ์ •์ƒ ์ฒ˜๋ฆฌ

 

 

4. JPA์—์„œ์˜ ๋‚™๊ด€์  & ๋น„๊ด€์  Lock

๊ฐ€์ •) ์ฟ ํฐ์ด 5์žฅ์ด ์žˆ๊ณ , 20๋ช…์˜ ์‚ฌ์šฉ์ž๊ฐ€ ๋™์‹œ์— ์ฟ ํฐ์„ ๋ฐœ๊ธ‰๋ฐ›์„ ๋•Œ๋ฅผ ๊ฐ€์ •

 

ํ…Œ์ŠคํŠธ๋ฅผ ์œ„ํ•ด Coupon ์—”ํ‹ฐํ‹ฐ์™€ Service ๋กœ์ง์„ ์ž‘์„ฑํ•ด์ฃผ์—ˆ๋‹ค. 

 

- Coupon

@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
public class Coupon {
    @Id
    @GeneratedValue
    @Column(name = "coupon_id")
    private Long id;

    private int count;

    @Builder
    public Coupon(int count) {
        this.count = count;
    }

    public void issue() {
        if (count <= 0) {
            throw new IllegalArgumentException("์ˆ˜๋Ÿ‰ ๋ถ€์กฑ");
        }
        count -= 1;
    }
}

 

DB์—” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋ฐ์ดํ„ฐ๊ฐ€ ๋“ค์–ด๊ฐ€์žˆ๋‹ค.

 

 

- case1: Lock์„ ๊ฑธ์ง€ ์•Š์•˜์„ ๋•Œ

    @Test
    @DisplayName("๋™์‹œ์— ์ฟ ํฐ์„ ๋ฐœ๊ธ‰ํ•  ๊ฒฝ์šฐ - Lock ์ ์šฉ x")
    void test_not_lock() throws InterruptedException {
        final int executeNumber = 20;

        ExecutorService executorService = Executors.newFixedThreadPool(10);

        // ์Šค๋ ˆ๋“œ์˜ ์‹คํ–‰์ด ๋๋‚  ๋•Œ๊นŒ์ง€ ๋Œ€๊ธฐ
        CountDownLatch countDownLatch = new CountDownLatch(executeNumber);

        AtomicInteger successCount = new AtomicInteger();
        AtomicInteger failCount = new AtomicInteger();

        for (int i = 0; i < executeNumber; i++) {
            executorService.execute(() -> {
                try {
                    couponService.issueCoupon(1);
                    successCount.getAndIncrement();
                    System.out.println("์ฟ ํฐ ๋ฐœ๊ธ‰");
                } catch (Exception e) {
                    failCount.getAndIncrement();
                    System.out.println(e.getMessage());
                }
                countDownLatch.countDown();
            });
        }

        countDownLatch.await();

        System.out.println("๋ฐœ๊ธ‰๋œ ์ฟ ํฐ์˜ ๊ฐœ์ˆ˜ = " + successCount.get());
        System.out.println("์‹คํŒจํ•œ ํšŸ์ˆ˜ = " + failCount.get());

        assertEquals(failCount.get() + successCount.get(), executeNumber);
    }

 

 

์œ„ ์ฝ”๋“œ์˜ ๊ฒฐ๊ณผ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋‚˜ํƒ€๋‚œ๋‹ค.

 

DB์—” ๋ถ„๋ช… ์ฟ ํฐ์ด 5๊ฐœ ๋ฐ–์— ์—†์ง€๋งŒ ๋ฐœ๊ธ‰๋œ ์ฟ ํฐ์˜ ๊ฐœ์ˆ˜๊ฐ€ 20๊ฐœ๋กœ ๋‚˜ํƒ€๋‚œ๋‹ค. ์ด๋Ÿฐ ๋ฌธ์ œ๋Š” ๋งค์šฐ ํฌ๋ฆฌํ‹ฐ์ปฌํ•œ ๋ฌธ์ œ์ด๋‹ค. 

์ด ๋ฌธ์ œ๋ฅผ ๋จผ์ € ๋‚™๊ด€์  Lock์„ ์‚ฌ์šฉํ•ด์„œ ํ•ด๊ฒฐํ•ด๋ณด์ž. 

 

 

 - case2: ๋‚™๊ด€์  Lock์„ ๊ฑธ์—ˆ์„ ๋•Œ

Integer version ๋ถ€๋ถ„์„ ์ œ์™ธํ•˜๊ณค ์œ„์—์„œ ์ž‘์„ฑํ•œ entity์™€ ๋™์ผํ•˜๋‹ค.

 

version์€ ์—”ํ‹ฐํ‹ฐ์— ์ ‘๊ทผํ•ด์„œ ์–ด๋– ํ•œ ๊ฐ’์ด ๋ณ€๊ฒฝ๋  ๋•Œ ์ด version์ด ๊ฐ™์ด ์˜ฌ๋ผ๊ฐ„๋‹ค. 

์ฆ‰, ํ˜„์žฌ ๋ฒ„์ „์ด ๋งž๋Š”์ง€ ์•„๋‹Œ์ง€ ์‹๋ณ„ํ•˜๊ธฐ ์œ„ํ•œ ์ˆซ์ž์ด๋‹ค. 

๋”ฐ๋ผ์„œ update ์ฟผ๋ฆฌ๊ฐ€ ๋‚˜๊ฐˆ ๋•Œ version์„ ํ™•์ธํ•˜๋Š” ์กฐ๊ฑด๋ฌธ์ด ํ•จ๊ป˜ ๋‚˜๊ฐ„๋‹ค. 

 

 

์•„๊นŒ ์ž‘์„ฑํ•œ Test ์ฝ”๋“œ๋ฅผ ๋Œ๋ฆฌ๋ฉด ๊ฒฐ๊ณผ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋‚˜์˜จ๋‹ค. 

 

์ด๋ฒˆ์—” ์™œ 3์žฅ์ธ ๊ฒƒ์ผ๊นŒ? ์‹ฌ์ง€์–ด 5์žฅ๋„ ์•„๋‹ˆ๋‹ค. ๊ทธ ์ด์œ ๋Š” ๋‚™๊ด€์  Lock์€ ์ตœ์ดˆ์˜ ์š”์ฒญ๋งŒ commitํ•˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ์ž์„ธํ•˜๊ฒŒ ์‚ดํŽด๋ณด์ž. 

 

๋‹ค์Œ๊ณผ ๊ฐ™์ด ํŠธ๋žœ์žญ์…˜ 4๊ฐœ๊ฐ€ ๋™์‹œ์— count์˜ ๊ฐ’์„ ๋ฐ”๊พธ๋ ค๊ณ  ํ•  ๋•Œ, ์ด๋•Œ์˜ version์€ 1์ด๋‹ค. 

 

 

 

ํŠธ๋žœ์žญ์…˜ A๊ฐ€ update๋ฌธ์„ ์ˆ˜ํ–‰ํ•  ๋•Œ version์„ ํ™•์ธํ•˜๊ณ  ํ˜„์žฌ์˜ version๊ณผ ๋™์ผํ•˜๊ธฐ ๋•Œ๋ฌธ์— version์„ 2๋กœ ์—…๋ฐ์ดํŠธ ์‹œ์ผœ์ค€๋‹ค. ๋‚˜๋จธ์ง€ ํŠธ๋žœ์žญ์…˜ B,C,D๋Š” ๋ณธ์ธ๋“ค์˜ version์€ 1์ธ ๋ฐ˜๋ฉด ๋ฐ์ดํ„ฐ์˜ version์€ 2๊ฐ€ ๋˜๊ธฐ ๋•Œ๋ฌธ์— ์‹คํŒจํ•˜๊ฒŒ ๋˜๋Š” ๊ฒƒ์ด๋‹ค. 

 

 

 

์œ„์™€ ๊ฐ™์€ ๋งค์ปค๋‹ˆ์ฆ˜์œผ๋กœ ๋™์ž‘ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋งค๋ฒˆ ๋ฐœ๊ธ‰๋˜๋Š” ํ‹ฐ์ผ“์˜ ๊ฐœ์ˆ˜๊ฐ€ ๋‹ค๋ฅด๊ฒŒ ๋‚˜ํƒ€๋‚  ์ˆ˜๋„ ์žˆ๋‹ค. 

ํ•˜์ง€๋งŒ ์—ฌ๊ธฐ์„œ ํ™•์‹คํ•œ ์ ์€ ์ ˆ๋Œ€ 5๊ฐœ๋ฅผ ๋„˜์ง€๋Š” ์•Š๋Š”๋‹ค๋Š” ๊ฒƒ์ด๋‹ค. 

 

 

- case3: ๋น„๊ด€์  Lock์„ ๊ฑธ์—ˆ์„ ๋•Œ

JPA์—์„  repository์˜ ์กฐํšŒ ํ•จ์ˆ˜์— Lock ์–ด๋…ธํ…Œ์ด์…˜์„ ๊ฑธ์–ด์ฃผ๋ฉด ๋น„๊ด€์  Lock์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. 

 

 

 

๋น„๊ด€์  Lock๊ฐ™์€ ๊ฒฝ์šฐ ์ •ํ™•ํžˆ 5๊ฐœ๋งŒ ์ฟ ํฐ์ด ๋ฐœ๊ธ‰๋œ๋‹ค. ๊ทธ ์ด์œ ๋Š” ๋น„๊ด€์  Lock์˜ ํŠน์ง•์— ์žˆ๋‹ค. 

๋น„๊ด€์  Lock์€ ๋ฐ์ดํ„ฐ์— ์ ‘๊ทผํ•จ๊ณผ ๋™์‹œ์— Lock์„ ๊ฑธ์–ด๋ฒ„๋ฆฌ๊ธฐ ๋•Œ๋ฌธ์— ํ•œ ๊ฐœ์˜ ํŠธ๋žœ์žญ์…˜์ด ๋ฐ์ดํ„ฐ์— ์ ‘๊ทผํ•˜๋Š” ์ˆœ๊ฐ„ ๋‚˜๋จธ์ง€ ํŠธ๋žœ์žญ์…˜์€ ์ „๋ถ€ ๋Œ€๊ธฐํ•˜๊ฒŒ ๋œ๋‹ค. 

 

 

 

์ฟผ๋ฆฌ๋ฅผ ์‚ดํŽด๋ณด๋ฉด select for update ์ฟผ๋ฆฌ๊ฐ€ ๋‚˜๊ฐ€๋Š”๊ฑธ ๋ณผ ์ˆ˜ ์žˆ๋‹ค. ์ด ์ฟผ๋ฆฌ๊ฐ€ DB์— ์ ‘๊ทผํ•จ์— ๋”ฐ๋ผ ์ฆ‰์‹œ Lock์„ ๊ฑฐ๋Š” ์ฟผ๋ฆฌ์ด๋‹ค. 

 

 

๋น„๊ด€์  Lock์˜ ๋™์ž‘ ๋งค์ปค๋‹ˆ์ฆ˜์„ ๊ทธ๋ฆผ์œผ๋กœ ์‚ดํŽด๋ณด๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

ํŠธ๋žœ์žญ์…˜ A๊ฐ€ ๋ฐ์ดํ„ฐ์— ์ ‘๊ทผํ–ˆ์„ ๋• ์ ‘๊ทผํ•˜์ž๋งˆ์ž Lock์„ ๊ฑธ์–ด๋ฒ„๋ฆฌ๊ธฐ ๋•Œ๋ฌธ์— ์–ด๋–ค ํŠธ๋žœ์žญ์…˜๋„ ๋™์ผํ•œ ๋ฐ์ดํ„ฐ์— ์ ‘๊ทผํ•  ์ˆ˜ ์—†๋‹ค. ํŠธ๋žœ์žญ์…˜ A๊ฐ€ ๋ณผ์ผ์„ ๋‹ค ๋ณด๊ณ  Lock์„ ํ‘ผ๋‹ค๋ฉด ๊ทธ๋•Œ ๋Œ€๊ธฐํ•˜๋˜ ํŠธ๋žœ์žญ์…˜์ด ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋‹ค.