트랜잭션 격리: PostgreSQL과 InnoDB에서 동시성 이상 진단하기
표준 격리 수준의 이름만 외우지 말고 엔진별 스냅샷·잠금 동작과 재시도 조건을 비교합니다. write skew와 격리 수준을 선택할 때 확인할 점도 예로 보여줍니다.
발행·오류 제보: infostoa · 편집 기준
이 글의 개별 작성자와 전문 검토자 정보는 제공되지 않습니다.
ACID는 자동으로 모든 업무 규칙을 보장하지 않습니다
ACID는 트랜잭션의 원자성·일관성·격리성·지속성을 설명하는 성질입니다. 특히 일관성은 DB가 업무 규칙을 저절로 추론한다는 뜻이 아닙니다. 제약 조건과 트랜잭션 설계가 규칙을 표현해야 합니다. 격리 수준도 엔진마다 구현이 달라 이름만 보고 동일한 동작을 가정하면 안 됩니다.
표준의 dirty read는 커밋되지 않은 값을 읽는 현상, non-repeatable read는 다시 읽은 기존 행 값이 달라지는 현상, phantom은 같은 조건의 행 집합이 달라지는 현상입니다. 이 세 현상 외에도 트랜잭션이 모두 성공적으로 커밋했는데도 직렬 순서로 설명할 수 없는 serialization anomaly가 있습니다.
격리 이름보다 엔진별 읽기 규칙을 확인하세요
| 설정·엔진 | 보통 읽기의 기준 | 주의할 결과 | 선택 시 확인 |
|---|---|---|---|
| PostgreSQL Read Committed (기본값) | 각 statement 시작 시 스냅샷 | 같은 트랜잭션의 두 SELECT가 서로 다른 커밋 상태를 볼 수 있음 | 여러 문장에 걸친 조건 확인을 단일 스냅샷으로 간주하지 않기 |
| PostgreSQL Repeatable Read | 첫 일반 statement 시점의 트랜잭션 스냅샷 | dirty/non-repeatable/phantom은 막지만 serialization anomaly는 남을 수 있고, 충돌 업데이트는 트랜잭션 실패로 이어질 수 있음 | 업무 불변식에 대한 write skew 가능성 및 전체 트랜잭션 재시도 처리 |
| PostgreSQL Serializable | 직렬 실행과 동등한 결과가 되도록 충돌 감시 | 잠금 대기만이 아니라 serialization failure가 날 수 있음 | SQLSTATE 40001에서 전체 트랜잭션을 다시 수행할 수 있는지 |
| MySQL InnoDB Repeatable Read (기본값) | 일반 consistent read는 첫 읽기에서 만든 스냅샷 | locking read와 일반 SELECT는 서로 다른 시점의 데이터를 볼 수 있음. 검색 조건·인덱스에 따라 next-key/gap lock이 삽입을 막을 수 있음 | SELECT ... FOR UPDATE/SHARE 사용 여부와 실행 계획의 잠금 범위 |
PostgreSQL의 Read Uncommitted 요청은 내부적으로 Read Committed처럼 동작합니다. PostgreSQL Repeatable Read는 표준이 요구하는 것보다 강해 phantom을 허용하지 않지만, 직렬화 이상까지 없애지는 않습니다. InnoDB Repeatable Read의 일반 SELECT는 스냅샷 읽기이며, 잠금 읽기와 UPDATE/DELETE는 별도 잠금 규칙을 적용합니다. 이를 “읽기는 절대 쓰기를 막지 않는다”로 일반화하면 안 됩니다.
가상 사례: 마지막 당직자 두 명이 동시에 퇴근하는 경우
당직자 두 명이 있고 최소 한 명은 계속 근무해야 한다고 가정합니다. 트랜잭션 A와 B가 각각 “근무 중 인원 2명”을 읽은 뒤 서로 다른 당직자 한 명씩 비활성화하면, 각 트랜잭션은 서로 다른 행을 바꾸므로 둘 다 커밋하면서 남은 인원이 0명이 될 수 있습니다. 이는 snapshot-based Repeatable Read에서 가능한 write skew의 예시이지 모든 엔진의 모든 설정에서 같은 결과가 난다는 주장은 아닙니다.
이 불변식을 지켜야 한다면 실제 엔진·버전에서 Serializable을 쓰고 serialization 실패 시 전체 업무 트랜잭션을 재시도할 수 있는지 검증하거나, 공통으로 잠글 단일 guard row/제약 설계를 검토하세요. 단순히 각 작업이 한 행만 갱신한다는 이유로 경쟁 안전성을 추론하지 마세요.
운영 점검 순서
- 제품·스토리지 엔진·버전·격리 설정을 기록합니다.
- 일반 SELECT인지 locking read인지, snapshot이 statement 단위인지 transaction 단위인지 확인합니다.
- 보호할 업무 불변식을 동시 실행 사례로 표현하고, lost update·write skew·phantom 중 어떤 위험인지 분류합니다.
- 제약이나 잠금, 더 높은 격리 수준을 선택한 뒤 deadlock/serialization 오류를 전체 트랜잭션 재시도로 처리합니다.
- 테스트 환경에서 동시 세션을 재현하고, 성능·오류율·재시도 비용을 실제 측정합니다.
사례는 설명을 위한 가상 시나리오이며 데이터베이스에서 실행하지 않았습니다. PostgreSQL과 InnoDB의 세부 동작은 아래 버전별 공식 문서를 기준으로 확인하세요.
자료: PostgreSQL 18 Transaction Isolation, MySQL 8.4 InnoDB Transaction Isolation, MySQL 8.4 Consistent Nonlocking Reads, MySQL 8.4 Locking Reads.