데이터 베이스에서 락이란
여러 트랜잭션이 동시에 같은 데이터를 읽고/수정하려고 할 때 **데이터의 정합성(일관성)**을 지키기 위해 “잠금”을 걸어 접근을 제어하는 메커니즘
핵심은 동시성(성능) ↔ 일관성(정확성) 균형
락이 필요한 대표 상황
- Lost Update(갱신 분실): A와 B가 같은 행을 읽고 각각 수정 → 나중에 커밋한 값이 앞선 변경을 덮어씀
- Dirty Read: 아직 커밋 안 된 데이터를 다른 트랜잭션이 읽음
- Non-repeatable Read / Phantom Read: 같은 조회를 두 번 했는데 중간에 다른 트랜잭션이 수정/삽입해서 결과가 달라짐
1) 락의 기본 종류: Shared / Exclusive
Shared Lock (S Lock, 읽기 락)
- “읽을게요” 잠금
- 여러 트랜잭션이 동시에 S락은 가능
- 하지만 누군가가 X락(쓰기)을 걸려 하면 충돌
Exclusive Lock (X Lock, 쓰기 락)
- “수정할게요” 잠금
- 단독으로만 가능 (다른 S/X 모두 막음)
- UPDATE/DELETE 같은 쓰기 작업에 필요
2) 락의 범위(Granularity)
DB는 상황에 따라 잠금을 “어디까지” 걸지 결정합니다.
- Row Lock(행 락): 특정 행만 잠금 → 동시성 좋음
- Page/Block Lock: 특정 페이지(블록) 단위 잠금
- Table Lock(테이블 락): 테이블 전체 잠금 → 동시성 나쁨, 대신 관리 단순/빠른 경우도 있음
- Index Lock / Key-range Lock(범위 락): 인덱스 범위를 잠가 팬텀을 막는 방식(격리수준 관련)
3) 트랜잭션 격리수준과 락의 관계
격리수준이 높을수록 “안전”하지만 보통 락/대기 증가로 성능 부담이 커집니다.
- READ UNCOMMITTED: 커밋 안 된 데이터도 읽을 수 있음(Dirty Read 가능)
- READ COMMITTED: 커밋된 것만 읽음(Dirty Read 방지)
- REPEATABLE READ: 같은 행을 반복 조회해도 동일(Non-repeatable 방지), DB에 따라 팬텀은 남을 수 있음
- SERIALIZABLE: 가장 강함(팬텀도 방지), 동시성 가장 떨어짐
실무에선 “격리수준”이 곧 “락/버전관리 전략”과 붙어 있고, DB마다 구현이 달라요. (예: 어떤 DB는 MVCC로 읽기 락을 줄임)
4) 비관적 락 vs 낙관적 락 (애플리케이션 관점)
비관적 락(Pessimistic)
- “충돌 날 것 같으니 미리 잠금”
- DB 락을 강하게 사용
- 예: SELECT ... FOR UPDATE (읽으면서 수정 예정이니 X락 걸어라)
장점: 충돌 시 데이터 꼬임 방지에 강함
단점: 대기/교착상태(Deadlock) 가능, 동시성 감소
낙관적 락(Optimistic)
- “충돌은 드물다, 커밋할 때 검사”
- 보통 version 컬럼으로 체크 (JPA의 @Version 같은 방식)
- 업데이트 시 WHERE id=? AND version=? 조건으로 성공/실패 판단
장점: 락 대기 적고 동시성 좋음
단점: 충돌 발생 시 재시도 로직 필요
5) 데드락(Deadlock)
서로가 서로의 락을 기다리며 영원히 못 움직이는 상태.
예)
- T1: A행 X락 → B행 X락 시도(대기)
- T2: B행 X락 → A행 X락 시도(대기)
→ 교착
대부분 DB는 데드락을 감지해서 한 트랜잭션을 롤백(희생자 선택) 시킵니다.
예방 팁(실무에서 진짜 중요)
- 항상 같은 순서로 락 획득(예: id 오름차순)
- 트랜잭션을 짧게 유지
- 인덱스를 잘 잡아 불필요한 범위 락/테이블 스캔 방지
- “한 번에 많이 갱신” 배치 작업은 chunking
6) (자주 헷갈리는 포인트) MVCC와 락
많은 현대 DB는 **MVCC(다중 버전 동시성 제어)**로 “읽기”는 과거 버전을 보게 해서 읽기 락을 줄입니다.
하지만 쓰기-쓰기 충돌이나 **특정 격리수준(팬텀 방지)**에서는 여전히 락이 필요합니다.
스프링/JPA 실무 기준으로 감 잡기
- 동시 수정이 자주 일어나는 “수량/좌석/재고/포인트” 같은 곳
- 충돌이 잦으면: 비관적 락(SELECT FOR UPDATE) 고려
- 충돌이 드물면: 낙관적 락(@Version) 선호
- “조회만 많고 수정이 적은” 화면
- MVCC 덕에 보통 큰 문제 없음
- “조회 후 계산해서 수정” 패턴(읽고 → 계산 → 업데이트)
- Lost Update 위험이 큼 → 락 전략 필수
* 개인적인 의문.
- 예전에 DB를 이용할때 DB에 다수의 인원이 다양한 요청을 보내 DB가 멈춰버리던 현상을 락에 걸렸다고 표현하는 걸 들은적이 있는데 이거과 다른점인지?
- 락(Lock) = DB가 동시성 제어를 위해 의도적으로 거는 잠금(정상 기능)
- DB가 멈춘 것처럼 보임 = 대개 락 때문에 “기다림”이 길어져서 그렇게 보이는 현상 (또는 교착, 과도한 부하)
둘은 같은 개념이 아니라, 원인(락)과 결과(멈춘 것처럼 보임) 관계
“DB가 멈춘다”가 실제로는 뭐였을 가능성이 큰가
1) 락 대기(Lock Wait)
- 어떤 트랜잭션이 행/테이블을 잡고 오래 안 놓음(커밋/롤백 안 함)
- 다른 트랜잭션들이 그 락이 풀릴 때까지 대기
- 앱에서는 API가 응답이 늦어지거나 타임아웃 → “DB 멈춤”처럼 체감
대표 원인
- 트랜잭션을 너무 길게 잡음 (대량 업데이트, 외부 API 호출을 트랜잭션 안에서 함)
- 인덱스가 없어 범위를 넓게 잠금(테이블 스캔) → 락 범위가 커짐
2) 데드락(Deadlock)
- 서로가 서로의 락을 기다리는 교착 상태
- DB가 “영원히 멈추게” 두진 않고, 보통 한쪽 트랜잭션을 강제 롤백시킴
- 앱에서는 간헐적으로 오류가 나거나 재시도가 필요
3) 리소스 병목(부하로 인한 정지처럼 보임)
이건 “락”이 원인이 아닐 수도 있어요.
- CPU 100%, 디스크 I/O 포화, 커넥션 풀 고갈, 슬로우 쿼리 폭증
- 결과적으로 요청이 쌓여서 “멈춤”처럼 보임
다만 락 대기도 결국은 대기 요청이 쌓여서 DB/애플리케이션 리소스를 같이 잡아먹기 때문에 부하처럼 보일 수 있습니다.
* 락을 거는 주체는?
대부분의 락은 DB가 “자동으로” 걸고, 필요할 때만 사람이 의도적으로 더 강하게 걸어 제어
1) DB가 자동으로 거는 락(기본)
트랜잭션에서
- UPDATE / DELETE / INSERT 하면 DB가 알아서 쓰기 락(X락) 걸고,
- 격리수준/엔진 방식에 따라 읽기에도 필요한 제어(MVCC 포함)를 자동.
그래서 보통 개발자가 “락을 걸자”라고 코드에 쓰지 않아도, 쓰기 작업 자체가 락을 동반
2) 사람이 의도적으로 거는 락(명시적/강제)
충돌이 잦거나 “읽고 나서 반드시 수정” 같은 흐름을 안전하게 만들려면 개발자가 명시적으로 락을 겁니다.
대표 예시
- 비관적 락: SELECT ... FOR UPDATE
→ “지금 읽는 이 행은 내가 곧 수정할 거니까 다른 수정 못 하게 잡아둬” - (DB별로) 테이블 락: LOCK TABLE ...
- JPA에선 @Lock(PESSIMISTIC_WRITE) 같은 걸로 걸기도 함
- 낙관적 락은 “락을 거는” 느낌이라기보다 version으로 충돌 감지를 사람이 설계하는 방식
3) 그럼 “락 문제”를 누가 만들기도 하냐?
락 자체는 정상 기능인데, 아래는 사람이 만든 코드/설계 때문에 락이 길어져서 사고가 납니다.
- 트랜잭션을 길게 잡음 (트랜잭션 안에서 외부 API 호출, 사용자 입력 대기 등)
- 인덱스가 없어 업데이트/조회 범위가 커짐 → 락 범위 확대
- 같은 자원을 서로 다른 순서로 잠금 → 데드락
- 한 번에 대량 갱신(배치)으로 많은 행을 오래 잠금
* DB별로 락을 거는 법과 락에 의해 발생하는 현상이 모두 다른지?
1) DB별 락 거는 법
MySQL (InnoDB)
행 잠금(비관적 락)
- SELECT ... FOR UPDATE : 조회한 행을 수정 목적으로 잠금
- SELECT ... FOR SHARE (또는 구버전 LOCK IN SHARE MODE) : 공유(읽기) 잠금
경합 줄이기 옵션
- ... FOR UPDATE NOWAIT : 못 잡으면 바로 실패
- ... FOR UPDATE SKIP LOCKED : 잠긴 행은 건너뛰고 가져오기 (큐/작업자 패턴에 자주 씀)
테이블 락
- LOCK TABLES t WRITE/READ (주의: InnoDB라도 테이블 락을 강제로 걸 수 있음)
애플리케이션(Advisory) 락
- GET_LOCK('name', timeout), RELEASE_LOCK('name') : “업무용 뮤텍스” 같은 개념
PostgreSQL
행 잠금(비관적 락)
- SELECT ... FOR UPDATE
- FOR NO KEY UPDATE / FOR SHARE / FOR KEY SHARE 처럼 더 세분화된 모드가 있음(외래키/키변경과 얽힐 때 의미가 커짐)
옵션
- NOWAIT, SKIP LOCKED 지원
테이블 락
- LOCK TABLE t IN ... MODE (여러 모드 존재)
Advisory 락
- pg_advisory_lock(...), pg_advisory_unlock(...) (세션/트랜잭션 단위 둘 다 가능)
Oracle
행 잠금
- SELECT ... FOR UPDATE [NOWAIT | WAIT n | SKIP LOCKED]
테이블 락
- LOCK TABLE t IN ... MODE
업무용 락(패키지)
- DBMS_LOCK 등을 이용한 사용자 정의 락도 많이 씀
SQL Server
SQL Server는 “구문”보다 힌트/트랜잭션 격리수준으로 제어하는 경우가 많아요.
행/범위 잠금 힌트(SELECT에 붙임)
- WITH (UPDLOCK) : 읽으면서 업데이트 락(수정 예정)
- WITH (XLOCK) : 배타 락
- WITH (HOLDLOCK) : 범위를 오래 유지(사실상 더 강한 일관성)
- ROWLOCK, PAGLOCK, TABLOCK 등으로 잠금 단위 힌트
Advisory 락
- sp_getapplock / sp_releaseapplock : 애플리케이션 락
2) DB별 락에 의한 현상
공통으로 나타나는 현상(대부분 DB에서 똑같이 체감)
- Blocking(락 대기)
- 어떤 트랜잭션이 락을 쥐고 있고 → 다른 트랜잭션이 줄줄이 대기
- 앱에서는 “DB 멈춘 듯” 보이는 가장 흔한 원인
- Deadlock(교착상태)
- 서로 필요한 락을 교차로 잡고 기다림
- 보통 DB가 감지해서 한쪽을 강제 롤백(에러 발생)시킴
- Lock timeout / statement timeout
- 일정 시간 기다리다 실패 (DB/설정/쿼리 옵션에 따라 메시지 다름)
DB마다 달라지는 “대표적인 차이 포인트”
1) 읽기가 막히는가? (MVCC/스냅샷 차이)
- PostgreSQL / Oracle: 기본적으로 읽기는 MVCC 기반이라 대부분의 경우 “쓰기 때문에 읽기가 막히는 느낌”이 덜함
(대신 오래된 트랜잭션이 남아 있으면 정리/성능 문제가 생길 수 있음) - MySQL(InnoDB): MVCC가 있지만, 격리수준/쿼리 패턴에 따라 FOR UPDATE 같은 잠금 읽기가 흔하고, 인덱스가 없으면 락 범위가 커지기 쉬움
- SQL Server: 기본 READ COMMITTED는 읽기/쓰기 경합이 더 보일 수 있고, 스냅샷 계열(예: RCSI/SNAPSHOT)을 켜면 양상이 확 달라짐
2) “범위 락/팬텀 방지” 방식이 다름
- MySQL(InnoDB): (특히 REPEATABLE READ에서) 인덱스를 기반으로 next-key(레코드+갭) 락 같은 “범위 잠금”이 생겨서 생각보다 많이 막히는 경우가 있음
→ 인덱스 유무/조건이 락 범위에 큰 영향 - PostgreSQL: SERIALIZABLE에서 내부적으로 충돌을 탐지하는 방식(SSI)이라, “잠금으로 막는다” 느낌이 아니라 커밋 시점에 실패(serialization failure)로 나타나는 경우가 있음
- Oracle: 읽기 일관성이 강하고, 팬텀/일관성은 방식이 다르게 체감됨(읽기가 잘 안 막히는 편)
3) 잠금 단위/에스컬레이션(승격)
- SQL Server는 “행→페이지→테이블”처럼 락 에스컬레이션이 이슈로 자주 등장합니다(설정/상황에 따라).
- MySQL/InnoDB, PostgreSQL도 잠금 단위 개념은 있지만, SQL Server만큼 “에스컬레이션 때문에 갑자기 테이블이 막힘”이 대표 밈처럼 자주 나오진 않는 편(대신 다른 요인이 큼).
4) “문제 징후”가 보이는 방식이 다름
- MySQL: “Lock wait timeout”, “Deadlock found…” 같은 메시지로 자주 인지
- PostgreSQL: “deadlock detected”도 있지만 SERIALIZABLE이면 “could not serialize access…”처럼 재시도를 전제로 한 실패가 나옴
- SQL Server: deadlock victim, lock request time out period exceeded 등
- Oracle: NOWAIT/WAIT 옵션에 따른 에러, 대기 이벤트로 보이는 경우 많음
“DB가 멈춘 것 같다”를 락으로 의심할 때 3가지
- **대기 체인(blocking chain)**이 있는지
- 오래 열린 트랜잭션이 있는지(커밋 안 하고 잡고 있는 세션)
- 인덱스 부재로 락 범위가 커진 것인지(조건 컬럼 인덱스 확인)
'Daily Dev Q&A' 카테고리의 다른 글
| http와 https의 차이 (0) | 2026.01.25 |
|---|---|
| JVM (0) | 2026.01.18 |
| application context (0) | 2026.01.16 |
| WAS와 웹서버 (0) | 2026.01.11 |
| Garbage Collection (0) | 2026.01.07 |