1. 정의
1-1 Write Skew란 두 개 이상의 트랜잭션이 동시에 동일한 조건을 읽고, 서로 다른 데이터를 수정 하면서 데이터 정합성 및 비즈니스 규칙이 깨지는 현상이다.
a. 동일한 조건을 읽고 같은 row의 다른 컬럼을 업데이트 하는 경우 데이터 정합성이 깨지는 예시

b. 동일한 조건을 읽고 다른 row의 업데이트를 시도해 비즈니스 규칙이 깨지는 예시

이번 글에서는 MySQL, PostgreSQL, MsSQL 각각 세 개의 데이터베이스에서 Write Skew가 발생하는 상황과, 락을 통해 이를 방지하는 방법에 대해 알아보겠다. (낙관적 락의 경우는 제외하겠다.)
3. DBMS별 Write Skew 동작
3.1 MySQL (InnoDB)
REPEATABLE READ
MySQL InnoDB의 REPEATABLE READ는 MVCC를 기반으로 동작합니다.
트랜잭션이 시작되면 해당 시점의 스냅샷 버전을 고정해서 읽기 때문에,
다른 트랜잭션이 데이터를 수정해도 현재 트랜잭션이 읽는 데이터에는 반영되지 않습니다.
-
같은 행(row) 수정
- 두 트랜잭션이 동일한 행을 UPDATE/DELETE하려고 하면 InnoDB의 Next-Key Lock(레코드 락 + 갭 락)이 작동합니다.
- 이때 두 번째 트랜잭션은 해당 행의 배타적 락을 얻을 수 없어 대기하거나, Deadlock이 감지되면 즉시 롤백됩니다.
- 즉, 동일한 행에 대한 쓰기 충돌은 즉시 감지되어 한쪽이 롤백됩니다.
-
다른 행(row) 수정
- 서로 다른 행을 수정하는 경우, MVCC 특성상 쓰기 충돌이 발생하지 않으면 충돌로 간주하지 않습니다.
- 즉, 동일 조건을 읽었더라도 수정 대상 행이 다르면 두 트랜잭션 모두 커밋이 가능하여 Write Skew가 발생할 수 있습니다.
- 이를 방지하려면
SELECT ... FOR UPDATE를 사용하여 명시적으로 해당 행에 락을 걸어야 합니다.
SERIALIZABLE
InnoDB에서는 모든 SELECT 문을 포함한 읽기 연산에도 공유락(Shared Lock) 을 부여하며,
해당 WHERE 조건이 접근하는 인덱스 범위 전체에 Next-Key Lock(=Record Lock + Gap Lock) 을 적용한다.
이로 인해 동일 조건을 만족하는 모든 레코드뿐 아니라,
그 주변 인덱스 갭까지 잠기므로
-
새로운 행 INSERT
-
기존 행 UPDATE
-
동일 조건 SELECT
모두 차단되며,결과적으로 Write Skew와 Phantom Read가 완전히 방지된다.
3.2 PostgreSQL
REPEATABLE READ
PostgreSQL의 REPEATABLE READ 역시 MVCC 기반입니다.
시작 시점의 스냅샷을 기준으로 읽으며, 충돌 감지 로직은 row-level 쓰기 충돌에만 의존합니다.
-
같은 행(row) 수정
- 두 트랜잭션이 동일한 행을 UPDATE하려 하면 PostgreSQL은 해당 행의 쓰기 충돌을 감지합니다.
- 충돌 시, 나중에 쓰기를 시도한 트랜잭션은 즉시 롤백됩니다.
이는 PostgreSQL이 쓰기 시점에 해당 행의 xmin/xmax 버전을 비교하여 충돌 여부를 판정하기 때문입니다.
-
다른 행(row) 수정
- 서로 다른 행을 수정하는 경우, PostgreSQL은 이를 충돌로 간주하지 않습니다.
- 따라서 동일 조건을 읽고 다른 행을 수정하면 Write Skew가 발생할 수 있습니다.
- 이 경우
SELECT ... FOR UPDATE 또는 SERIALIZABLE 격리 수준이 필요합니다.
SERIALIZABLE
- PostgreSQL은 SERIALIZABLE에서 SSI(Serializable Snapshot Isolation) 알고리즘을 사용합니다.
- 트랜잭션 간 읽기→쓰기, 쓰기→읽기 의존성을 실시간 추적합니다.
- 커밋 시 순환 의존(cycle)이 발생하면, 이를 직렬 실행 불가능 상황으로 판단하고 즉시 롤백합니다.
- 이 방식은 서로 다른 행을 수정하더라도 규칙 위반 가능성이 있으면 차단할 수 있습니다.
3.3 MSSQL
REPEATABLE READ
MSSQL의 REPEATABLE READ는 잠금 기반으로 동작합니다.
-
같은 행(row) 수정
- 특정 행을 읽을 때 공유락(S Lock)을 걸고, 트랜잭션 종료 시까지 유지합니다.
- 다른 트랜잭션이 해당 행을 수정하려고 하면 배타적 락을 얻지 못해 대기하거나 Deadlock이 발생합니다.
- 따라서 동일한 행 수정은 차단됩니다.
-
다른 행(row) 수정
- 범위 잠금(range lock)이 없으므로, 같은 조건으로 조회하더라도 다른 행의 수정이나 삽입은 가능하여 Write Skew가 발생할 수 있습니다.
(다만 실행 계획·인덱스 조건에 따라 Key-Range Lock이 잡히는 예외가 있어서 “절대 없음”이라고 단정할 수는 없습니다.)
- 이를 방지하려면
WITH (UPDLOCK, HOLDLOCK)을 사용해 범위 잠금을 걸어야 합니다.
SERIALIZABLE
- MSSQL의 SERIALIZABLE은 읽은 행뿐 아니라 조회 조건에 해당하는 전체 범위에 잠금을 겁니다.
- 이를 Range Lock이라고 하며, 이 범위 내의 INSERT/UPDATE/DELETE를 모두 차단합니다.
- 결과적으로 Phantom Read와 Write Skew 모두 방지됩니다.
4. 비교 표
DBMS 격리수준별 Write Skew & Phantom Read 동작
| DB / 격리수준 | 동일 행 수정 차단 | 다른 행 수정 차단 (Write Skew) | Phantom Read | 비고 |
|---|
| MySQL RR | ⭕ 행 락 충돌 감지 | 🔺 가능 (FOR UPDATE 필요) | ❌ 방지됨 (Next-Key Lock) | MVCC + Next-Key Lock으로 팬텀 리드 자동 방지 |
| MySQL SERIALIZABLE | ⭕ | ⭕ | ❌ 방지됨 | 모든 읽기에 공유락 + Next-Key Lock |
| PostgreSQL RR | ⭕ 행 락 충돌 감지 | 🔺 가능 (FOR UPDATE 필요) | 🔺 가능 | MVCC만 사용, 갭 락 없음 → 팬텀 발생 가능 |
| PostgreSQL SERIALIZABLE | ⭕ | ⭕ | ❌ 방지됨 | SSI 알고리즘으로 팬텀·Write Skew 차단 |
| MSSQL RR | ⭕ 읽기 시 공유락 유지 | 🔺 가능 (범위락 없음) | 🔺 가능 | 실행 계획 따라 예외적으로 범위락 걸릴 수 있음 |
| MSSQL SERIALIZABLE | ⭕ | ⭕ | ❌ 방지됨 | Key-Range Lock으로 팬텀·Write Skew 차단 |
범례
- ⭕ = 해당 현상 항상 방지
- 🔺 = 잠금 옵션을 추가로 사용해야 방지 가능
- ❌ = 해당 현상 격리수준에서 차단됨
5. 핵심 요약
- 같은 행 수정:
REPEATABLE READ 이상이면 대부분 방지 가능
- 다른 행 수정: MySQL, PostgreSQL은 MVCC로 조회는 안전하지만 쓰기 충돌 감지 불가 → 잠금 필요
- MSSQL REPEATABLE READ는 Phantom Read 가능
- SERIALIZABLE은 모든 DBMS에서 Write Skew, Phantom Read 방지