들어가기 앞서
https://www.youtube.com/watch?v=EBBS_giQ4AM
오늘은 데이터베이스 락에 대해 알아볼 예정입니다. 데이터베이스 락은 동시성 이슈가 발생할때 보통 사용이 됩니다. 하지만 성능 저하가 심하기 때문에 사용시 많은 주의가 필요하고, 대체할 수 있는 방안이 있기에 잘 사용하지 않는데요.
그래도 데이터베이스 락은 가장 기본적인 개념이기 때문에 중요하다고 생각합니다. 또한 일반적으로 락을 개인적으로 설정하지 않아도 데이터베이스에서 default로 락이 설정되는 경우도 있어, 동작원리를 이해하고 있는 것이 중요하다고 생각합니다.
데이터베이스 락(lock)
데이터베이스 락(lock)은 여러 트랜잭션이 동시에 같은 데이터에 접근할 때, 데이터의 정합성과 일관성을 보장하기 위해 사용되는 동기화 메커니즘입니다. 간단히 말해, 하나의 트랜잭션이 어떤 데이터를 사용하는 동안 다른 트랜잭션이 그 데이터에 접근하지 못하도록 막는 장치입니다.

동시성 대해서 간단하게 설명하면 다음과 같습니다. 결제 서비스에서 내 통장 잔고는 1만원이 있습니다. 두 요청에서 1만원씩 결제를 요청한다면, 하나의 요청만 성공 하고, 하나의 요청은 실패 해야 합니다. 하지만 결제 요청이 정말 짧은 시간에 같이 발생하면 어떻게 될까요?
잔금을 조회하는 시점에는 둘다 1만원이 있어, 1만을 차감하고 결제를 성공했다고 처리합니다. 이러한 동시성 문제를 해결하기 위해 데이터베이스의 락이 필요합니다.
MySQL에서의 락 개념은 MySQL 엔진 레벨 락과 스토리지 엔진 레벨 락이 있습니다.
MySQL Engine 락
MySQL 자체에서 관리하는 락이며, 주로 MyISAM 같은 트랜잭션을 지원하지 않는 스토리지 엔진에서 사용됩니다.
1. 글로벌 락
- MySQL 전체 인스턴스에 락을 거는 방식입니다.
- MySQL에서 지원하는 락 중에 가장 큰 락이며, MySQL 서버 전체에 영향을 미치게 됩니다.
- 특정 세션에서 글로벌 락을 획득하게 되는 경우, 해당 세션을 제외한 다른 세션에서는 단순 조회를 제외하고 대부분의 명령문을 실행할 수 없습니다.
- 주로 백업 시점에 데이터의 정합성을 보장하기 위해 사용됩니다.
- 또한 글로벌 락은 MySQL의 모든 변경 작업을 중단시킨다는 단점이 있습니다.
2. 백업 락
- 글로벌 락의 단점인 모든 변경 작업을 중단해야 하는 점을 개선하였습니다.
- 백업 락은 글로벌 락보다 더 가볍고 효율적인 락으로, InnoDB에만 적용되며 DML은 허용하되 DDL만 제한합니다.
3. 테이블락
- 하나의 테이블에 대해 읽기 또는 쓰기 락을 거는 방식입니다.
- 여러 스레드가 동시에 같은 테이블을 사용하지 못하도록 막는 기능입니다.
- LOCK TABLES ... READ/WRITE 문을 통해 수동으로 락을 걸 수 있으며, MyISAM에서는 쿼리 실행 시 자동으로 테이블 락이 발생합니
- 테이블 구조를 변경할 때 묵시적으로 락을 얻을 수 있습니다. (DDL)
- 데이터를 변경할 때도 묵시적으로 락을 얻을 수 있습니다. (DML, 단 InnoDB 제외)
Storage Engine(InnoDB) 락
InnoDB는 트랜잭션을 지원하므로 훨씬 더 정교한 락 메커니즘을 제공합니다. 데드락 이슈와 밀접하게 관련된 세 가지 주요 락이 있습니다.
1. 레코드 락
앞서 MySQL 엔진 락에서 테이블락은 데이터를 변경할 때 InnoDB만 제외하고 적용된다고 하였는데, 그 이유가 InnoDB에서는 레코드락이 적용되기 때문입니다. 조금 더 정확하게는 각 레코드의 인덱스에 락이 발생하게 됩니다. 만약 인덱스가 걸려 있지 않는 컬럼으로 변경 작업을 수행한다면 모든 레코드에 락이 걸리게 됩니다. 따라서 레코드 락은 인덱스 설정이 매우 중요합니다.

- 특정 행(Row)에만 걸리는 락입니다.
- SELECT ... FOR UPDATE와 같은 명령으로 사용할 수 있으며, 주로 배타 락(Exclusive Lock)으로 사용됩니다.
- 동시에 여러 트랜잭션이 다른 행을 업데이트할 수 있게 하므로 동시성이 매우 높습니다.
SELECT * FROM user WHERE id = 1 FOR UPDATE;
2. 갭 락
갭 락이란, 레코드와 인접한 레코드 사이에 새로운 데이터를 생성할 수 없도록 하는 락입니다.

- 행과 행 사이의 "빈 공간(Gap)"에 락을 거는 방식입니다.
- 인덱스를 기준으로 존재하지 않는 값에 대한 삽입을 방지하기 위해 사용됩니다.
- 예: 잔고 BETWEEN 1,000 AND 20,000으로 검색했을 때, 존재하지 않는 id=15,000의 삽입을 막고자 할 때 갭에 락이 걸립니다.
- 주로 반복 가능 읽기(REPEATABLE READ) 격리 수준에서 동작합니다.
SELECT * FROM 통장 WHERE 잔고 >= 1000 AND 잔고 <= 20000 FOR UPDATE;
3. 넥스트 키 락
MySQL의 default 트랜잭션 격리수준은 Repeatable Read입니다. Repeatable Read는 Phantom Read가 발생하는데, MySQL에서는 Phantom Read가 발생하지 않습니다. 그 이유는 레코드 락과 갭 락의 특징으로 인해 발생하지 않습니다.
- 레코드 락 + 갭 락이 결합된 형태입니다.
- 즉, 해당 인덱스 레코드와 그 앞의 갭까지 모두 잠급니다.
- 데드락이나 팬텀 리드(phantom read)를 방지하기 위해 사용됩니다.
- REPEATABLE READ 격리 수준에서는 기본적으로 Next-Key Lock이 사용됩니다.

select *
from 통장
where 잔고 >= 20,000원 and 잔고 <= 40,000원 for update
해당 쿼리로 인해 20,000원 ~ 40,000원 사이에 갭 락이 발생합니다. 하지만 인접한 레코드들도 함께 락이 걸리기 때문에 실제 락은 15,000원 ~ 40,000원 사이에 갭 락이 걸리게 됩니다. 또한 20,000, 40,000, 30,000, 각각에 레코드 락이 걸리게 됩니다. (헷갈리면 안되는 부분이 갭락은 15,000원은 포함 되지만, 50,000원은 갭락에 포함되지 않습니다. 왼쪽에는 락이 열려 있지만 오른쪽으로는 락이 닫혀 있습니다.)
insert into 통장 (은행, 이름, 잔고)
values ('KB', '금명', 5,000원)
해당 쿼리로 'KB', '금명', '5000' 레코드를 삽입하게 됩니다.
insert into 통장 (은행, 이름, 잔고)
values ('KB', '금명', 25,000원)
트랜잭션 1이 commit 되는 동안 트랜잭션 2는 락이 걸린 범위에 write를 하기 때문에 wait하게 됩니다. 그리고 트랜잭션 2가 begin 하였을 때는 이미 '금명'은 유니크키로 레코드가 삽입 되어 있기 때문에 실패하게 됩니다. 여기서 중요한 점은 유니크 키로 실패하는 것에 집중하는 것이 아닌, 트랜잭션 1에 의해 적용된 넥스트 키 락으로 인해 트랜잭션 2가 대기한다는 것이 중요합니다.
마지막으로 MySQL의 innoDB에서는 데드락이 발생할 가능성이 있을 경우, 뒤 늦게 시작된 트랜잭션은 롤백을 통해 데드락 상황을 회피합니다.
'테크톡 딥다이브' 카테고리의 다른 글
| DBCP (Database Connection Pool) (0) | 2025.04.15 |
|---|---|
| 스프링 Transaction 전파 속성 (0) | 2025.03.31 |
| 네이버의 Fixture Monkey 사용기 (5) | 2025.02.24 |
| 의미 있는 테스트 코드에 대해 알아보자 (0) | 2025.02.24 |
| 카카오 선물하기 팀의 캐싱 전략 (하이브리드 캐시와 캐시 웜업 자동화) (1) | 2025.01.07 |