데이터베이스 락은 동시성 환경에서 데이터 무결성과 일관성을 보장하기 위해 사용된다. 특히 MySQL과 같은 RDBMS에서는 여러 사용자가 동시에 데이터에 접근할 때 충돌을 방지하기 위해 다양한 락 메커니즘을 제공한다.
1. 락의 필요성
여러 트랜잭션이 동일한 데이터를 동시에 수정하거나 조회하면서 발생할 수 있는 문제를 방지한다. 대표적으로 아래와 같은 문제가 있다.
- 더티 리드(Dirty Read): 한 트랜잭션이 커밋되지 않은 데이터를 읽음으로써 발생하는 문제
- 비반복 읽기(Non-repeatable Read): 같은 쿼리를 두 번 수행했을 때 결과가 달라지는 문제
- 팬텀 리드(Phantom Read): 조건에 부합하는 새로운 행이 다른 트랜잭션에 의해 추가되면서 생기는 문제
2. 락의 종류
2.1 테이블 레벨 락
- 테이블 전체를 잠가 다른 세션이 읽기나 쓰기를 못 하게 한다.
- MyISAM 엔진에서 기본으로 사용되며, 처리 성능은 낮지만 단순하다.
LOCK TABLES users WRITE;
-- 작업 수행
UNLOCK TABLES;
2.2 행 레벨 락 (Row-level Lock)
- InnoDB 엔진에서 지원되며, 특정 행만 잠근다.
- 동시성은 높지만 락 관리에 따른 오버헤드가 크다.
BEGIN;
SELECT * FROM users WHERE id = 1 FOR UPDATE;
-- 작업 수행 후
COMMIT;
3. InnoDB vs MyISAM
3.1 InnoDB
InnoDB는 MySQL에서 가장 널리 사용되는 스토리지 엔진으로, 트랜잭션 처리, 외래 키(Foreign Key), 충돌 복구, 행 수준 락(row-level locking) 등을 지원한다.
- ACID 특성을 보장하는 트랜잭션을 지원한다.
- MVCC(Multi-Version Concurrency Control)를 기반으로 높은 동시성을 제공한다.
- 자동 크래시 복구와 Undo/Redo 로그를 통한 복구 기능을 제공한다.
- B-Tree 기반의 클러스터형 인덱스를 사용한다.
MySQL 5.5부터는 InnoDB가 기본 스토리지 엔진으로 설정되어 있으며, 대부분의 락과 트랜잭션 관련 기능은 InnoDB를 통해 구현된다.
3.2 MyISAM 엔진
MyISAM은 기본적으로 트랜잭션을 지원하지 않지만, 테이블 레벨 락을 사용하여 동시성 문제를 해결하려 한다. MyISAM은 빠른 읽기 성능을 제공하지만, 쓰기 성능에서는 InnoDB에 비해 상대적으로 떨어진다.
- 트랜잭션을 지원하지 않기 때문에 ACID 속성 중 일부를 보장할 수 없다.
- 읽기 작업에서는 매우 빠른 성능을 보이며, 이를 통해 주로 읽기 비중이 높은 애플리케이션에 사용된다.
- 갑작스러운 서버 중단 시, InnoDB처럼 자동 복구 기능을 제공하지 않기 때문에, 복구가 어려운 경우가 있다.
3.3 InnoDB와 MyISAM 비교
특성 InnoDB MyISAM
트랜잭션 지원 | 지원 (ACID 보장) | 미지원 |
락 메커니즘 | 행 레벨 락 (Row-level Locking) | 테이블 레벨 락 (Table-level Locking) |
동시성 | 높은 동시성 | 낮은 동시성 |
성능 (읽기) | 보통 | 빠름 |
성능 (쓰기) | 느리지만 트랜잭션 보장 | 빠름 |
외래 키 지원 | 지원 | 미지원 |
복구 기능 | 자동 복구 지원 | 복구 불가 |
MyISAM은 읽기 성능이 뛰어나고, 테이블 단위로 간단한 락을 지원하는 반면, InnoDB는 트랜잭션과 행 레벨 락을 통해 높은 동시성을 제공하며, ACID 특성을 보장한다.
4. InnoDB 락 메커니즘
4.1 갭 락(Gap Lock)
- 존재하지 않는 레코드 영역을 잠가 트랜잭션 간 삽입 충돌을 방지한다.
SELECT * FROM users WHERE age > 30 FOR UPDATE;
- 위 쿼리는 age > 30인 영역의 갭을 잠가 다른 트랜잭션이 그 사이에 삽입하지 못하게 한다.
4.2 넥스트키 락(Next-key Lock)
- 실제 행과 그 앞의 갭을 동시에 잠그는 락이다.
- InnoDB의 Repeatable Read에서 기본적으로 사용된다.
4.3 의도 락(Intention Lock)
- 테이블 락과 행 락의 충돌을 방지하기 위해 상위 객체인 테이블에 사전 의도를 표시한다.
- 종류
- IS (Intention Shared) → 트랜잭션이 행에 대한 S lock을 획득하기 전에 먼저 테이블에 대한 IS lock을 획득해야 함
- IX (Intention Exclusive) → 트랜잭션이 행에 대한 X lock을 취득하기 전에 먼저 테이블에 대한 IX lock을 획득해야 함
5. 격리 수준과 락
5.1 READ UNCOMMITTED
- 가장 낮은 수준
- 다른 트랜잭션에서 커밋되지 않은 데이터를 읽을 수 있음
- 락 없음 혹은 최소한의 락
5.2 READ COMMITTED
- 커밋된 데이터만 읽을 수 있음 → Dirty Read 해결
- 비반복 읽기 문제 발생
- SELECT 시 공유 락 없음
5.3 REPEATABLE READ
- InnoDB의 기본 격리 수준
- 같은 트랜잭션 내에서 같은 데이터를 반복 읽어도 동일한 결과를 보장 → 비반복 읽기 문제 해결
- 넥스트키 락 + MVCC로 팬텀 리드를 방지 → 하지만 완전히 피할 수는 없음 (SELECT 이후 SELECT FOR UPDATE 시 아래와 같은 시나리오에서 팬텀 리드 발생)
1. 트랜잭션 A: SELECT * FROM users WHERE age > 20 실행 → SELECT이므로 락 X
2. 트랜잭션 B: INSERT INTO users (name, age) VALUES ('Tom', 25) 실행 → 커밋
3. 트랜잭션 A: SELECT * FROM users WHERE age > 20 FOR UPDATE 실행 → 이때 팬텀 레코드(‘Tom’)가 보이고, 이제서야 락을 시도하게 된 것
5.4 SERIALIZABLE
- 가장 엄격한 격리 수준
- 모든 읽기 작업에도 공유 락을 설정
- 동시성이 급격히 저하되지만 팬텀 리드를 완벽히 방지
6. 데드락과 해결 방법
데드락이란?
- 트랜잭션 A가 트랜잭션 B의 락을 기다리고 있고, 동시에 B도 A의 락을 기다리는 상태
해결 방법
- InnoDB는 자동으로 데드락을 감지하고 하나의 트랜잭션을 강제 롤백
예방 전략
- 동일한 순서로 자원을 접근
- 짧은 트랜잭션 유지
- 필요할 때만 락 사용
- 인덱스를 적절히 활용해 잠금 범위 최소화
'개발 > CS' 카테고리의 다른 글
무중단 배포 전략 (1) | 2025.07.16 |
---|---|
Transaction Application Flow (1) | 2025.06.14 |
Python은 Call by value일까, Call by reference일까? (2) | 2024.05.22 |
트랜잭션이란? (1) | 2024.02.19 |
(CS) 무중단 배포 (0) | 2024.01.08 |