Table lock vs Row lock
실무에서 실수하지 않는 것이 목표이며, 시나리오 중심으로 개념을 공고히 합니다.
개념 정리
구문에 따라서 lock되는 데이터의 범위 & 양이 달라질 수 있다.
Table locks
테이블 전체를 잠근다.
데드락이 발생하지 않는다.
Row-level locking을 하더라도 조건 필드에 인덱스가 없으면 전체 테이블에 lock이 걸린다.
MySQL이 조회된 행을 기억하는 방법이 인덱스 범위를 기억하는 것이기 때문.
많은 수의 Row-level locking을 시도할 때 Table locking이 더 효율적이라면, 전체 테이블에 lock이 걸릴 수도 있다. 이는
Lock Escalation
이라고 한다.
Single-row locks
트랜잭션 격리 레벨에 따라 다르게 적용된다.
데드락이 발생하지 않도록 주의해야한다. 특히 innoDB나 BDB 타입의 테이블들은 deadlock-free하지 않으므로 주의할 것.
사용
Table locks
사용 방법
READ lock인지 WRITE lock인지 설정할 수 있다.
Rails에서는 Table lock을 공식적으로 지원하지는 않는다. 그래서 아래처럼 직접 SQL문을 넣어줘야한다.
사용 시나리오
Table lock은 전체 테이블에 대한 데이터 변경이 있을 경우 사용한다. 테이블을 제어하는 DDL 구문을 사용할 때 Lock이 걸린다고 하여 DDL Lock이라고도 한다.
운영 중인 테이블을 복제(CREATE SELECT)하거나 다른 테이블로 옮길 경우(INSERT SELECT) Transaction Isolation Level을 READ COMMITTED 변경하고 작업하기를 권장한다.
그렇지 않으면 관련된 Table은 Exclusive Lock이 걸리고, 관련 Query들이 대기 상태로 빠지면서 시스템 장애가 발생할지도 모르기 때문.
Row locks
사용방
FOR UPDATE
를 붙여주면 Exclusive lock을 건다. 이 때 MySQL은 Row 전체에 lock을 거는 것이 아니라 인덱스에 건다고 하니 참고.
lock 모드를 SHARE MODE로 걸 수도 있다.
ActiveReocrd에서는 lock
구문이나 with_lock
구문을 이용하면, 위 FOR UPDATE
SQL 구문을 생성해준다.
사용 시나리오
특정 user의 reward 컬럼 값을 100만큼 증가시키는 API가 있다고 하자. 그리고 user.reward
가 원래 0원인 상태에서 200원이 되도록 하기 위해 이 API를 동시에 2차례 호출한다고 생각해보자. 그리고 이 API 호출을 각각 1번 API 호출, 2번 API 호출로 명명해보겠다.
만약 이 API에 lock 처리가 되어있지 않은 상태라면, 이 1번 호출, 2번 호출이 동시에 진행되었을 때 문제가 생길 수 있다.
1번 호출과 2번 호출에서 user 값을 read해올 때, 아직 update가 되기 전의 값을 read 해올 수 있기 때문.
구체적으로는
user.reward
를 0원으로 불러오고, 결과적으로 두 요청 모두user.reward
값을 0원에서 100원으로 증가시킨 뒤 최종적으로 100원이 된 상태에서user.save
를 호출하게 되어, 0원에서 200원이 되는 것이 아니라 0원에서 100원이 될 수 있다.
대신 Exclusive lock 처리를 하면, 1번 호출, 2번 호출이 동시에 진행되더라도 문제를 방지할 수 있다.
1번 호출과 2번 호출에서 user 값을 read해올 때, 다른 호출에서 lock을 들고 있으면, lock이 풀릴 때까지 기다렸다가 값을 read해오기 때문에, 순차적으로 reward 값을 증가시킬 수 있기 때문이다.
참고
Last updated