본문 바로가기
DB

[DB] 트랜잭션 정리 2편 - 락(Lock)

by 개미가되고싶은사람 2024. 8. 8.

목차

    1.  트랜잭션 락이 생긴 이유

    세션1이 트랜잭션을 시작하고 데이터를 수정은 했지만 아직 커밋을 수행하지 않았는데, 세션2에서 동시에 같은 데이터를 수정하게 되면 데이터 일관성에 위배되는 문제가 발생합니다. 이런 문제를 해결하기 위해 트랜잭션에 락이라는 기능이 등장하게 되었습니다.

     

    2. 트랜잭션 락이란?

    세션이 트랜잭션을 시작하고 데이터를 수정하는 동안에는 커밋이나 롤백 전까지 다른 세션에서 해당 데이터를 수정하지 못하게 권한을 주지 않는 것을 트랜잭션 락이라고 합니다. 그래서 트랜잭션 락은 데이터의 일관성과 무결성을 유지하게 도와줍니다.

     

     

    3. 트랜잭션 락 동작과정

    해당 설명은 세션1이 세션2보다 조금 더 빨리 요청했다는 가정하여 설명한 내용입니다.

     

    1. 세션1은 트랜잭션을 시작한다.

    2. 세션1은 memberA 의 money 를 500으로 변경을 시도한다. 이때 해당 로우의 락을 먼저 획득해야 한다. 락이 남아 있으므로 세션1은 락을 획득한다.

    3. 세션1은 락을 획득했으므로 해당 로우에 update sql을 수행한다.

    4. 세션2는 트랜잭션을 시작한다.

    5. 세션2도 memberA 의 money 데이터를 변경하려고 시도한다. 이때 해당 로우의 락을 먼저 획득해야 한다. 락이 없으므로 락이 돌아올 때 까지 대기한다.

    참고로 세션2가 락을 무한정 대기하는 것은 아니다. 락 대기 시간을 넘어가면 락 타임아웃 오류가 발생한다. 락 대기 시간은 설정할 수 있다.

    6. 세션1은 커밋을 수행한다. 커밋으로 트랜잭션이 종료되었으므로 락도 반납한다.  락을 획득하기 위해 대기하던 세션2가 락을 획득한다.

    7. 세션2는 update sql을 수행한다. 

    8. 세션2는 커밋을 수행하고 트랜잭션이 종료되었으므로 락을 반납한다.

     

     

    트랜잭션 락 개념은 update 쿼리문에만 적용되는게 아니고 select문에도 적용이 될 수 있습니다. 일반적으로  select문을 사용할 때 락을 획득하지 않아도 바로 데이터를 조회할 수 있습니다. 그럼 select문에서 락을 사용하는 방법과 어떤 경우에 사용하는지 알아보겠습니다.

     

     

    4. 트랜잭션 락 - select문

    4-1 select문에서 트랜잭션 락 사용 방법 - select for update 

    데이터를 조회할 때 락을 획득하고 싶으면 select .... for update 구문을 사용하면 됩니다.

    ex) SELECT * FROM member WHERE user_id = 2 FOR UPDATE;

     

    4-2 select문에서 트랜잭션 락을 사용하는 이유

    ex) 은행 계좌 송금

    -- 트랜잭션 A: 사용자 A에서 사용자 B로 송금
    START TRANSACTION;
    
    -- 사용자 A의 계좌 잠금 
    SELECT balance FROM accounts WHERE user_id = 1 FOR UPDATE;
    
    -- 사용자 B의 계좌 잠금 
    SELECT balance FROM accounts WHERE user_id = 2 FOR UPDATE;
    
    -- 송금 작업 수행
    UPDATE accounts SET balance = balance - 100 WHERE user_id = 1;
    UPDATE accounts SET balance = balance + 100 WHERE user_id = 2;
    
    COMMIT;
    
    -- 트랜잭션 B: 사용자 B에서 사용자 A로 송금
    START TRANSACTION;
    
    -- 사용자 B의 계좌 잠금
    SELECT balance FROM accounts WHERE user_id = 2 FOR UPDATE;
    
    -- 사용자 A의 계좌 잠금
    SELECT balance FROM accounts WHERE user_id = 1 FOR UPDATE;
    
    -- 송금 작업 수행
    UPDATE accounts SET balance = balance - 50 WHERE user_id = 2;
    UPDATE accounts SET balance = balance + 50 WHERE user_id = 1;
    
    COMMIT;

     

    트랜잭션 A: 사용자 A의 계좌에서 사용자 B의 계좌로 금액을 송금하는 작업을 수행합니다.

    트랜잭션 B: 사용자 B의 계좌에서 사용자 A의 계좌로 금액을 송금하는 작업을 수행합니다.

     

    해당 예시는 다른 블로그에서도 흔하게 볼 수 있는 은행 송금 예시입니다. 필자는 처음에 해당 예시를 보면서 왜? select절에 트랜잭션 락이 필요한거지 생각습니다. update절에는 트랜잭션 락 기능이 있는데 굳이 왜 select절에도 필요한걸까 생각해봤는데 총 3가지에 이유를 찾을 수 있었습니다.

     

    1. 잠금 시점의 제어(일관성 유지) - 경합 조건 방지
    update문에서만 잠금을 걸면, 데이터가 읽히고 나서 update가 실행되는 사이의 짧은 시간 동안에도 다른 트랜잭션이 데이터를 변경할 수 있는 가능성이 있습니다. 


    2. 트랜잭션의 충돌 방지(동시성 제어) - 경합 조건 방지
    여러 트랜잭션이 동시에 실행되면서 같은 데이터를 읽고 각각 업데이트를 시도하면 충돌이 발생할 수 있습니다.

    예를 들어, 두 트랜잭션이 같은 계좌의 잔액을 읽고 각각 다른 값으로 업데이트하려고 할 때 문제가 발생할 수 있습니다.

     

    3. 데드락 방지
    두 개 이상의 트랜잭션이 서로 다른 순서로 데이터를 잠그면 데드락이 발생할 수 있습니다.

    예를 들어, 트랜잭션 A가 계좌 A를 잠그고 계좌 B를 잠그려는 동안, 트랜잭션 B는 계좌 B를 잠그고 계좌 A를 잠그려는 상황에서 데드락이 발생할 수 있습니다.

    이러한 이유 때문에  select문에서 트랜잭션 락을 사용하면 데이터 일관성 유지 , 동시성 제어, 데드락을 방지할 수 있습니다.

     

     

     

    'DB' 카테고리의 다른 글

    [DB+Spring] JdbcTemplate 정리 - NamedParameterJdbcTemplate  (2) 2024.08.16
    [DB] 트랜잭션 정리 1편  (0) 2024.08.06
    [DB] SQL Mapper와 ORM 이란?  (0) 2024.08.03