Daily Dev Q&A

데이터 베이스의 락

awake123 2026. 2. 1. 22:23

데이터 베이스에서 락이란

여러 트랜잭션이 동시에 같은 데이터를 읽고/수정하려고 할 때 **데이터의 정합성(일관성)**을 지키기 위해 “잠금”을 걸어 접근을 제어하는 메커니즘

 

핵심은 동시성(성능) ↔ 일관성(정확성) 균형

 

락이 필요한 대표 상황

  • 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에서 똑같이 체감)

  1. Blocking(락 대기)
  • 어떤 트랜잭션이 락을 쥐고 있고 → 다른 트랜잭션이 줄줄이 대기
  • 앱에서는 “DB 멈춘 듯” 보이는 가장 흔한 원인
  1. Deadlock(교착상태)
  • 서로 필요한 락을 교차로 잡고 기다림
  • 보통 DB가 감지해서 한쪽을 강제 롤백(에러 발생)시킴
  1. 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가지

  1. **대기 체인(blocking chain)**이 있는지
  2. 오래 열린 트랜잭션이 있는지(커밋 안 하고 잡고 있는 세션)
  3. 인덱스 부재로 락 범위가 커진 것인지(조건 컬럼 인덱스 확인)

'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