락은 데이터베이스의 무결성 유지를 위해 중요한 개념이다. 락의 개념 중 대표격으로 나뉘어지는 공유 락과 배타 락에 대해 포스팅하도록 하겠다.
공유 락(Shared Lock)은 "읽는 동안 수정을 막는 락"이라고 생각하면 편하다. 한 트랜잭션에서 어떤 데이터에 대해 공유 락을 들고 있으면, 다른 트랜잭션에서는 이 데이터에 대한 갱신을 수행할 수 없고, 오직 읽기만 가능하다. 공유 락이 걸린 데이터가 있을 때, 다른 트랜잭션도 이 데이터에 대해 공유 락을 획득할 수 있으나, 배타 락을 획득할 수는 없다는 것이다.(아래에서 설명하겠지만 배타 락은 쓰기 위한 락이기 때문이다.)
배타 락(Exclusive Lock)는 "쓰는 동안 수정을 막는 락"이라고 생각하면 편하다. 한 트랜잭션에서 어떠한 데이터에 대해 배타 락을 획득하면, 다른 트랜잭션은 해당 데이터에 대해 공유 락이나 배타 락을 획득할 수 없다.
그러나 주의할 점은, 공유 락을 획득할 수 없다는 것은 읽기를 막는다는 뜻이 아니다. 이는 밑에서 직접 설명하겠다.
공유 락
공유 락은 select 문에 for share
를 추가하는 형태로 획득할 수 있다.
select (열) from (테이블) for share
배타 락
배타 락은 select 문에 for update
를 추가하는 형태로 획득할 수 있다.
select (열) from (테이블) for update
그렇다면 h2 database에서 별개의 스레드를 통해 배타 락을 걸어보도록 하겠다.(참고로 h2는 공유 잠금만 거는 문법이 존재하지 않는다.)
스레드 A를 시작하고 set autocommit false;
를 통해 트랜잭션을 시작한다
select * from item for update
를 실행하여 item 테이블의 레코드에 대한 배타 락을 획득한다.
다른 콘솔에서 스레드 B를 시작하고, 동일하게 select * from item for update
를 통해 배타 락을 획득하려 하지만, 이미 스레드 A가 해당 데이터에 대한 배타 락을 가지고 있으므로, 락 획득을 기다리다가 타임아웃이 나게 된다.
배타 락이 걸린 데이터에 update
쿼리를 실행해봤지만 역시 락이 풀리기를 기다리고 있다가 타임아웃이 나고 만다.
rollback
혹은 commit
으로 트랜잭션을 종료시키고 나서야 update 쿼리가 정상적으로 실행되는 것을 확인할 수 있다.
가끔 한 트랜잭션에서 배타 락을 획득하면 다른 트랜잭션에서 갱신 및 조회가 불가능하다고 인식하는 경우가 있는데, 이는 옳지 않다. 위의 사진은 스레드 A에서 트랜잭션을 시작한 후, 배타 락을 획득한 경우다.
그러나 위와 같이 스레드 B에서 배타 락이 획득된 데이터에 대해 select
를 수행했더니 결과가 정상적으로 조회되는 것을 확인할 수 있다.
select
쿼리는 단순한 행위에 해당하고, 락을 거는 것과는 전혀 다르다. 배타 락이 걸린 데이터에 대해 다른 스레드에서 공유 락과 배타 락을 얻을 수 있는 것이지, 여전히 커밋된 데이터에 대해 락 옵션을 걸지 않은 단순 select
를 사용한다면 조회할 수 있다.
공유 락
배타 락
https://velog.io/@soongjamm/Select-%EC%BF%BC%EB%A6%AC%EB%8A%94-S%EB%9D%BD%EC%9D%B4-%EC%95%84%EB%8B%88%EB%8B%A4.-X%EB%9D%BD%EA%B3%BC-S%EB%9D%BD%EC%9D%98-%EC%B0%A8%EC%9D%B4
https://hudi.blog/mysql-8.0-shared-lock-and-exclusive-lock/
개발자로서 배울 점이 많은 글이었습니다. 감사합니다.