코드프레소 백엔드 개발자 양성 과정

데이터베이스 잠금 기능

트랜잭션 중 DBMS 내에서 벌어지는 잠금 현상에 대해 알아본다.

H Lee
5 min readMay 16, 2020
Image by eyeball3000 on Pexels

온라인으로 영화관 좌석을 예매할 때, 위치는 선택했지만 결제는 마치지 않은 애매한 시점이 있기 마련이다. 그런데 만약 같은 좌석을 나보다 나중에 선택한 사람이 결제를 더 빨리 해서 낚아채 버렸다면? 기분이 좋지 않은 건 둘째 치고 데이터베이스가 꼬이기 쉽상이다. 그래서 일반적인 서비스에서는 찜한 좌석이 가령 10분 동안만 유효하다는 메시지와 함께 데이터베이스를 잠근다. 이 잠금이란 것에 대해 알아본다.

잠금이란?

삽입, 삭제, 갱신 등의 트랜잭션이 일어나는 동안 DBMS의 테이블, 행이나 열 등의 요소들은 잠기게 된다. 다른 트랜잭션을 통해 그 자료에 대한 CRUD 작업을 일절 또는 때에 따라 부분적으로 수행할 수 없다는 뜻이다. 이는 데이터베이스의 필수 성질인 원자성, 일관성, 고립성, 내구성을 유지하기 위해 트랜잭션이 순차적으로 진행되도록 해 준다.

잠금 기능은 DBMS 내의 각 개체와 단계를 대상으로 적용된다. 적용 대상은 다음과 같은 계층 구조를 가진다.

이 계층 구조는 효율성을 따져서 적절히 이용해 준다. 예를 들어 잠가야 할 행이 많아 한 페이지를 이룰 정도라면, 차라리 페이지 단위에 잠금을 걸고 작업을 수행하는 편이 비용이 적게 든다. 하지만 잠기는 범위가 넓으면 넓을 수록 자원에 접근 및 이용 제한이 가해지는 것이므로 기회 비용을 고려하여 잠금 범위를 정해 준다.

잠금의 종류

잠금에는 아래 주요 두 종류가 있다.

  • 공유 잠금 (Shared Lock)

이 잠금은 객체를 조회하여 읽을 때 적용된다. 여러 사용자가 같은 데이터를 동시에 열람하는 것을 허용하기에 ‘공유 잠금’이라는 이름이 붙었다.

  • 배타적 잠금 (Exclusive Lock)

이 잠금 방식은 다른 어떤 트랜잭션도 잠긴 객체를 수정하거나 열람하는 것을 방지한다. 데이터베이스에 데이터를 삽입, 수정, 삭제할 때 사용된다.

데이터베이스를 잠그는 행위에는 두 가지 장점이 있다. 첫째, 트랜잭션 중 오류 발생 시, 시스템이 롤백을 수행할 수 있도록 시작점을 기억해두는 역할을 한다. 둘째, 데이터가 삽입, 삭제, 갱신되고 있을 때 클라이언트가 데이터를 조회하는 ‘더티 리드’ (dirty read) 불상사를 방지한다.

하지만 그렇기 때문에 트랜잭션이 진행되는 동안은 잠긴 데이터에 접근할 수 없다는 단점도 발생한다. 이를 보완하기 위해 트랜잭션을 최대한 빨리 처리하는 것이 관건이며, 인덱스로 하여금 새로 삽입되거나 삭제된 데이터를 반영하도록 최적화해주는 것이 중요하다.

MySQL의 잠금 기능

MySQL에서는 InnoDB 엔진 이용 시 행 단위 잠금까지 지원하며, MyISAM 등 이외의 엔진 이용 시 테이블 단위의 잠금만 지원한다. 일반적으로 잠금은 DBMS에 의해 자동적으로 적용되지만 LOCK TABLE문을 이용해 명시적으로 테이블을 잠글 수 있다.

문법은 다음과 같다.

LOCK TABLES table_name [READ | WRITE];

예시를 보자.

우선 다음과 같은 명령문으로 현재 접속한 채널의 연결 식별자를 파악한다.

SELECT connection_id();

이 경우는 10이다. 이 세션을 세션 10이라고 하자.

SELECT connection_id();

간단한 테이블을 만든다.

LOCK TABLE문으로 잠근 후, READ 설정은 공유 잠금으로, 읽기는 허용한다.

LOCK TABLE table_name READ;

그리고 그 테이블에 새로운 행을 추가해 본다.

LOCK TABLE messages READ;

오류가 발생함을 확인할 수 있다. 읽기만 허용하는 잠금이 적용되어 데이터를 갱신하는 작업은 수행할 수 없다는 설명이 표시된다. 반면 조회문은 정상적으로 실행된다.

다른 세션을 열어 같은 실험을 해 본다. 두 번째로 연 세션의 연결 식별자는 14인 것을 확인할 수 있다. 이 세션은 세션 14라고 하자.

SELECT connection_id();

마찬가지로 세션 10에서 잠근 messages 테이블에 새 행을 삽입하는 명령을 내려본다. 가벼운 작업임에도 불구하고 진행 상태가 한동안 이어진다.

이때, SHOW PROCESSLIST; 명령어를 이용하여 서버가 처리 중인 항목을 살펴보면, 세션 14의 삽입문이 테이블 잠금이 해제되기를 기다리고 있는 상태임을 볼 수 있다.

SHOW PROCESSLIST;

세션 14로 돌아와 보면 이내 서버와 연결이 끊기는 오류가 발생했다고 출력되면서 삽입에 실패한다. 반면 세션 10에서와 같이 조회문은 정상적으로 실행된다.

이런 기능 덕에 온라인으로 예매를 할 때나 물건을 구매할 때, 내가 결제 중인 상품이 다른 이에 의해 갑자기 낚아채이지 않는 것이며 데이터베이스 기록에도 혼선이 빚어지지 않는 것이다.

--

--

No responses yet