[DB] 데이터베이스 락(Locking) & 동시성 제어
데이터베이스에서 Lock은 여러 트랜잭션(데이터베이스 작업 단위)이 동시에 데이터에 접근하고 수정하는 상황에서 데이터의 일관성과 무결성을 보장하기 위해 사용되는 개념
DB Lock의 Level
- 행 수준 잠금(Row-level Locking) : 데이터를 개별 행 단위로 잠금을 설정하는 것을 말한다. 이 경우에는 특정 행만 잠겨서 다른 트랜잭션은 해당 행에 접근할 수 없게 된다.
- 테이블 수준 잠금(Table-Level Locking) : 테이블과 인덱스에 모두 잠금을 설정. Select table, Alter table, Vacuum, Refresh,Index, Drop, Truncate 등의 작업에서 해당 레벨의 락이 설정된다.
- 데이터베이스 수준 잠금(Database-Level Locking) : 데이터베이스를 복구하거나 스키마를 변경할 때 발생
🔐 1. Global Lock
MySQL 인스턴스(서버) 전체에 영향을 주는 락
- FLUSH TABLES WITH READ LOCK (FTWRL) 명령으로 획득할 수 있다.
- SELECT 쿼리를 제외한 대부분의 DDL이나 DML 쿼리를 실행할 수 없다.
- 데이터 베이스의 구조적 변경, 백업 수행 또는 크리티컬한 데이터 마이그레이션 작업 시에 사용된다.
- 그 중 백업 시 자주 사용되며, 모든 테이블에 대한 읽기 락을 걸어 새로운 쓰기 작업을 차단한다. 이 락이 걸린 동안에는 DML(insert, update 등) 작업이 차단되며, 읽기만 가능하다.
데이터 마이그레이션 (Data Migration)
한 시스템에서 다른 시스템으로 데이터를 이전하는 과정
보통 시스템 교체, 업그레이드, 통합, 클라우드 이전 등 다양한 이유로 수행된다.
Backup Lock
- LOCK INSTANCE FOR BACKUP
- MySQL이 8.0부터 InnoDB가 기본 Storage 엔진이 되면서 조금 더 가벼운 글로벌 락의 필요성이 생겼다.
- 논리적 백업 대신 물리적 백업을 위해 사용되는 락으로, FTWRL 과 달리 InnoDB의 내부 기능과 통합되어 세분화된 백업 시점 제어가 가능하다.
- InnoDB는 트랜잭션을 지원하기 때문에 일관된 데이터 상태를 위해 모든 데이터의 변경 작업을 멈출 필요가 없어졌다.
- 이러한 이유로 Backup Lock이 도입
LOCK INSTACE FOR BACKUP
-- // 백업 실행
UNLOCK INSTANCE
- 백업 락은 일반적인 테이블의 데이터 변경은 허용되고 스키마 변경 같은 DDL 명령어가 실행되면 복제를 일시 중지하는 역할을 한다.
(2. Database Lock)
- MySQL에서는 명시적인 데이터베이스 단위 락은 존재하지 않지만, 논리적인 분류로 이해하는 경우가 많다.
- 예를 들어, 어떤 관리 작업(DDL)이 특정 DB의 여러 테이블에 동시에 영향을 미친다면, 전체 DB 수준의 영향을 줄 수는 있다.
- 하지만 실제 락 수준은 테이블 락이나 MDL로 구현된다.
🔐 3. Table Lock
- 전체 테이블에 대한 잠금으로 특정 테이블의 모든 행에 대한 잠금을 설정한다.
- 주로 대량의 데이터를 추가, 업데이트 또는 삭제하는 배치 작업에서 유리하며, 이러한 작업 중에는 다른 작업의 간섭을 최소화할 필요가 있다.
LOCK TABLES table_name [AS alias] {READ | [READ LOCAL] | WRITE}
- (명시적) 명령으로 특정 테이블의 락을 획득할 수 있으며 MyISAM 뿐만 아니라 InnoDB에서도 사용이 가능하며 명시적으로 획득한 잠금은 UNLOCK TABLES 명령으로 잠금을 반납할 수 있다.
- (묵시적) 묵시적인 테이블 락은 스키마를 변경하는 쿼리(DDL)을 사용하는 경우에 발생하며, 이 경우에는 쿼리가 완료된 후 자동으로 락이 해제된다.
Intention Lock
- MySQL InnoDB 엔진에는 intention lock의 개념도 존재
- row에 대해서 나중에 어떤 row-level 락을 걸 것을 알려주기 위해 미리 table-level에 걸어두는 lock
- Intention Lock은 Row Lock이 아니다! → 트랜잭션이 이 테이블의 어떤 행에 락을 걸려고 하니 알아두라는 선언적 의미
- Intention Shared Lock (IS)
- SELECT ... LOCK IN SHARED MODE 가 실행되면
- Intention Shared Lock이 테이블에 걸림
- row-level 에 S-lock이 걸림
- SELECT ... LOCK IN SHARED MODE 가 실행되면
- Intention Exclusive Lock (IX)
- SELECT ... FOR UPDATE, INSERT, DELETE, UPDATE 이 실행되면
- Intention Exclusive Lock 이 테이블에 걸림
- row-level 에 X-Lock 이 걸림
- SELECT ... FOR UPDATE, INSERT, DELETE, UPDATE 이 실행되면
🔐 4. Row Level Lock
- InnoDB 스토리지 엔진에서 제공하는 가장 세분화된 락 수준
- 트랜잭션 처리 시 특정 행 단위에 대한 락으로 동시성이 높은 환경에서 유리
- 행 단위로 잠금을 관리함으로 여러 트랜잭션이 서로 다른 행을 동시에 처리 가능
4-1. Shared Lock (S Lock, 공유 락)
- 데이터를 읽을 때 사용하는 Lock
- SELECT ... LOCK IN SHARE MODE 등의 쿼리에서 사용
- 하나의 트랜잭션이 데이터를 읽고 있는 경우, 다른 트랜잭션들도 해당 데이터를 읽을 수는 있지만 쓰기 작업은 금지된다.
- 데이터의 일관성을 유지할 수 있다.
- 트랜잭션 격리수준 중 Repeatable Read 단계와 연관
4-2. Exclusive Lock (X Lock, 배타 락)
- 데이터를 변경하고자 할 때 사용되며, 트랜잭션이 완료될 때까지 유지된다.
- UPDATE, DELETE 등에서 자동으로 사용되며, 다른 트랜잭션이 해당 행에 접근하지 못하도록 완전히 잠근다.
- (특정 트랜잭션이 데이터를 수정하고 있는 경우, 해당 데이터에 대한 다른 모든 트랜잭션의 접근이 금지된다.)
- 트랜잭션 격리 수준 중 Serializable 단계와 연관
row-level 및 table-level 에서 두 번 Lock 을 하는 이유
- A 트랜잭션에서 이미 테이블에 대해 락이 걸려있는데, B 트랜잭션에서 해당 테이블의 특정 row에 대한 lock을 거는 것을 원천적으로 방지할 수 있다. (반대의 경우도 마찬가지)
- EX) row-level의 write이 일어나고 있을 때, 테이블 스키마가 변경되서는 안된다. write query의 경우 이미 IX 락을 획득한 상태이기 때문에 해당 테이블의 스키마가 변경되는 것을 막을 수 있다.
🔒 테이블 락 모드 호환성 (Lock Compatibility Matrix)
X (Exclusive) | IX (Intent Exclusive) | S (Shared) | IS (Intent Shared) | |
X | Conflict | Conflict | Conflict | Conflict |
IX | Conflict | Compatible | Conflict | Compatible |
S | Conflict | Conflict | Compatible | Compatible |
IS | Conflict | Compatible | Compatible | Compatible |
- Conflict: 락을 걸 수 없음 (기존 락과 충돌)
- Compatible: 락을 걸 수 있음 (동시에 존재 가능)
부연 설명
- 예시:
row-level의 write(예: UPDATE, INSERT)가 발생할 경우, 이미 해당 row에 대해 IX 락이 걸린 상태이기 때문에, 이 시점에서 테이블 스키마 변경(X 락 필요)은 Conflict로 인해 허용되지 않는다. - 의미 요약:
락 호환성은 동시 작업의 안전성과 정합성을 보장하기 위한 메커니즘이다. IX 락은 의도된 하위 레벨의 락(예: row-level exclusive)에 대한 신호이며, X 락은 전체 자원에 대한 독점 락을 의미하므로, IX가 존재하면 X 락은 충돌하게 된다.
Escalation
- 잠금 수준을 최적화하는 과정
- Lock Escalation 은 데이터베이스 시스템이 특정 Lock Level에서 다른 Lock Level로 전환하는 과정을 의미한다.
- 일반적으로 특정 트랜잭션에서 많은 수의 잠금을 설정하려는 경우 시스템은 이를 관리하기 위해 Lock Level을 높일 수 있다.
- Lock 레벨이 낮을 수록 동시성이 좋아지지만, 관리해야할 Lock이 많아지기 때문에 메모리 효율성은 떨어진다.
- 반대로 Lock 레벨이 높을 수록 관리 리소스는 낮지만, 동시성은 떨어진다.
Blocking (블로킹)
- Lock 간의 경합(Race Condition)이 발생하여 특정 Transition이 작업을 진행하지 못하고 대기하는 상태
- 공유 락끼리는 블로킹이 발생하지 않지만 베타 락은 블로킹을 발생시킨다.
- 특정 데이터에 공유 Lock이 설정된 상태에서 해당 데이터에 배타 Lock을 설정하려고 할 때
- 특정 데이터에 배타 Lock이 설정된 상태에서 해당 데이터에 공유 Lock을 설정하려고 할 때
- 블로킹을 해소하기 위해서는 이전 트랜잭션이 완료 (commit / rollback)이 되어야 한다.
- 위 그림에서 Coupon 데이터에 트랜잭션 A가 S-Lock을 설정한 후에 트랜잭션 B가 X-Lock을 설정하여 트랜잭션 B가 블로킹 상태에 진입한 것을 알 수 있다.
- 트랜잭션 A:
SELECT ... FOR SHARE 실행 → S-Lock (공유 락) 획득
→ 읽기 전용으로 id=1인 행을 보고 있음 (변경은 안 함) - 트랜잭션 B:
SELECT ... FOR UPDATE 실행 → X-Lock (배타 락)을 걸려고 시도
→ 하지만 이미 A가 S-Lock을 걸고 있어서 X-Lock을 획득할 수 없음
🔒 왜 Block 되는가?
- S-Lock과 X-Lock은 서로 호환되지 않음
→ S-Lock은 읽기 전용이고, X-Lock은 쓰기 및 수정을 하기 위한 락이기 때문 - A가 S-Lock을 걸고 있는 동안에는:
- 다른 트랜잭션이 또 다른 S-Lock은 가능
- 하지만 X-Lock은 불가능 (쓰기 금지)
- 그래서 트랜잭션 B는 A가 트랜잭션을 커밋하고 락을 해제할 때까지 기다려야 함
💡 핵심 포인트
- 공유 락(S-Lock) → 읽기 전용, 서로 겹쳐도 OK
- 배타 락(X-Lock) → 쓰기, 다른 어떤 락과도 겹치면 안 됨
- 그래서 트랜잭션 A가 끝나기 전까지 B는 블로킹 당하는 거고, 이것이 락 충돌로 인한 트랜잭션 대기 상황이다.
해결 방법
- SQL 문의 리팩토링
- 트랜잭션을 가능한 짧게 정의
- 동일한 데이터를 동시에 변경하는 작업을 하지 않도록 설계
- 대용량 작업이 불가피한 경우, 작업 단위를 쪼개거나 lock_timeout을 설정
Dead Lcok (데드락, 교착상태)
- 두 개의 트랜잭션 간의 각각의 트랜잭션이 가지고 있는 리소스의 Lock을 획득하려고 할 때 발생
- 각각의 트랜잭션에 Lock을 걸고 상대방 Lock에 접근하여 반환 받지 못하는 상황에서
- 서로 상대방의 락이 풀리기를 기다리는데, 그 락을 각자 상대가 들고 있어서 영원히 진행되지 않는 Dead Lock 이 발생하게 된다.
해결 방법
- Dead Lock 이 감지되면 둘 중 하나의 트랜잭션을 강제 종료한다.
- 실제로, Oracle 에서는 데드락이 감지되면 한쪽 Transaction을 강제로 풀어버린다.
- 이렇게 되면 하나의 트랜잭션 A의 마지막 실행 내용에 오류가 발생되고 커밋이 발생되도록 유지한다.
- 또 다른 트랜잭션 B는 아직 대기 중이며, 트랜잭션 A의 커밋을 기다린다.
- Dead Lock 방지를 위해 접근 순서를 동일하게 하는 것이 중요하다. → 접근 순서 규칙을 정한다.
🔸 Blocking vs DeadLock
구분 | 블로킹 (Blocking) | 데드락 (Deadlock) |
정의 | 한 트랜잭션이 다른 트랜잭션의 락 해제를 기다리는 상태 | 둘 이상의 트랜잭션이 서로의 락을 기다리며 무한 대기에 빠지는 상태 |
대기 구조 | 단방향 대기 (한 쪽이 기다림) | 순환 대기 (A → B → A 형태로 서로 기다림) |
지속 여부 | 선행 트랜잭션이 커밋/롤백되면 해소됨 | 양쪽 다 기다리기 때문에 외부 개입 없이는 해소되지 않음 |
발생 조건 | 일반적인 트랜잭션 상황에서 자주 발생 가능 | 네 가지 조건이 충족될 때만 발생 (자원 점유, 대기, 비선점, 순환 대기) |
해결 방법 | 기다리면 됨 (락 해제되면 진행됨) | DBMS가 감지해서 트랜잭션 중 하나를 강제 종료(rollback) |
💡 Ex - Blocking (블로킹)
- 트랜잭션 A가 row에 S-Lock을 걸고 읽는 중
- 트랜잭션 B가 동일 row에 X-Lock을 걸려고 시도
→ A가 끝날 때까지 B는 기다림 → 이것이 블로킹
💡 Ex - Deadlock (데드락)
- 트랜잭션 A가 row 1에 락을 걸고, row 2의 락을 요청
- 동시에 트랜잭션 B가 row 2에 락을 걸고, row 1의 락을 요청
→ A는 B가 가진 락을 기다리고, B는 A가 가진 락을 기다림
→ 서로 영원히 기다리는 상태 → Deadlock
블로킹은 일시적인 기다림, 데드락은 서로 영원히 기다리는 교착 상태
Optimistic Lock (낙관적 락)과 Pessimistic Lock (비관적 락)
낙관적 락 : "먼저 처리하고, 나중에 확인"
비관적 락 : "먼저 잠그고, 나만 처리"
Optimistic Lock
- 데이터 갱신 시 충돌이 발생하지 않을 것으로 간주하는 방식
- 데이터를 읽을 때 잠금을 설정하지 않고, 데이터를 변경하기 전에 충돌을 검출하는 방식이다.
- 주로 데이터 충돌이 적은 상황에서 사용
Pessimistic Lock
- 데이터 갱신 시 충돌이 발생할 것으로 보고 미리 잠금을 하는 방식
- 데이터를 읽을 때나 변경할 때 바로 잠금을 설정하여 다른 트랜잭션의 접근을 막는 방식이다.
- 무결성에 장점이 있지만 데드락의 위험성이 존재한다.
구분 | 낙관적 락 (Optimistic Locking) | 비관적 락 (Pessimistic Locking) |
개념 | 충돌이 드물다고 가정하고, 데이터에 락을 걸지 않음 | 충돌이 자주 발생한다고 가정하고, 미리 락을 설정함 |
락 방식 | 실제 수정 시점에 버전 체크 | 데이터 접근(읽기/쓰기) 시점에 락을 설정 |
충돌 처리 | 충돌 시 예외 처리 및 재시도 | 충돌 자체를 차단하여 동시성 문제를 방지 |
사용 방식 | 버전 번호 또는 타임스탬프를 이용해 변경 확인 JPA 사용시 @Version |
DB 수준의 SELECT ... FOR UPDATE 같은 명령 사용 Mode 설정 및 쿼리에 직접 사용, |
성능 | 충돌이 적으면 성능 우수 | 다중 사용자 환경에서 락 경합 발생 시 성능 저하 가능 |
적합한 상황 | 읽기 위주, 충돌이 드문 시스템 (예: 게시판, 통계 등) | 동시 업데이트 가능성이 높은 환경 (예: 은행 거래 등) |
장점 | 데드락 가능성이 적으며 성능의 이점 | 충돌에 대한 오버헤드가 줄어들며 무결성을 지키기 용이 |
단점 | 충돌이 발생하면 오버헤드 발생 | 충돌이 없으면 오버헤드가 발생 |
예시 | 버전 필드 비교 후 UPDATE | 트랜잭션 내 SELECT ... FOR UPDATE 사용 |
낙관적 락 / 비관적 락 vs 공유 락 / 배타적 락
- 낙관적 락과 비관적 락은 데이터 충돌의 빈도와 대응 방식을 나타내며
- 공유 잠금과 배타적 잠금은 데이터의 읽기와 쓰기 작업 간의 관계를 나타낸다.
Reference
https://kamja-ming.tistory.com/entry/DB-%EB%9D%BDLock%EC%9D%B4%EB%9E%80
[DB] 락(Lock)이란?
💡 락(Lock)이란? 데이터베이스는 여러 사용자들이 같은 데이터를 동시에 접근하는 상황에서, 데이터의 무결성과 일관성을 지키기 위해 사용 💡 락(Lock)의 종류 크게는 공유락과 배타적락으로
kamja-ming.tistory.com
https://ksh-coding.tistory.com/121
[DB] DB Lock이란? (feat. Lock 종류, 블로킹, 데드락)
0. 락(Lock)이란? 여러 커넥션에서 동시에 동일한 자원을 요청할 경우 순서대로 하나의 커넥션만 변경할 수 있게 해주는 기능 동시성을 제어하기 위한 기능 저는 처음에 DB 락을 접했을 때, 락을 이
ksh-coding.tistory.com