SQL Server SNAPSHOT 트랜잭션이 읽은 뒤 다른 트랜잭션이 그 행을 수정하고 커밋하면, 처음 트랜잭션이 같은 행을 수정할 때 update conflict가 발생할 수 있습니다. Database Engine은 해당 SNAPSHOT 트랜잭션을 종료하고 롤백합니다.

SNAPSHOT 격리를 사용하려면 대상 데이터베이스에서 ALLOW_SNAPSHOT_ISOLATION을 켜야 합니다. 실패한 트랜잭션은 애플리케이션이 새 트랜잭션에서 업무 조건을 확인해 다시 처리해야 합니다.

SNAPSHOT은 트랜잭션이 볼 행 버전을 고정합니다

SQL Server SNAPSHOT 격리에서 읽기는 트랜잭션 단위의 커밋된 행 버전을 기준으로 합니다. 트랜잭션이 읽기 스냅샷을 잡은 뒤 다른 세션이 행을 바꾸고 커밋해도, 기존 트랜잭션은 읽기에서 그 변경보다 앞선 버전을 봅니다. 그래서 두 번 읽은 값이 같을 수 있지만, 이 일관성이 다른 세션의 쓰기까지 허용한다는 뜻은 아닙니다.

두 SNAPSHOT 트랜잭션이 같은 오래된 행을 읽고 각각 수정하려고 한다면, 먼저 커밋한 쪽과 나중에 쓰려는 쪽의 대상 행이 달라졌는지 확인해야 합니다. SQL Server 문서는 스냅샷 바깥에서 대상 행이 수정된 경우 update conflict를 발생시키고 SNAPSHOT 트랜잭션을 종료한다고 설명합니다. 실패를 숨기고 오래된 계산값을 덮어쓰게 두는 대신 충돌을 드러내는 낙관적 동시성 제어입니다.

읽기·수정·커밋 순서를 표에 적습니다

교육용으로 재고 한 행을 가정해 보겠습니다. 값은 처음에 10이고, T1은 SNAPSHOT 격리에서 10을 읽어 9로 줄이려 합니다. T1이 쓰기 전에 T2가 같은 재고를 8로 바꾸고 커밋합니다. 이제 T1이 자기 스냅샷에서 본 행을 갱신하려고 하면, SQL Server는 현재 행이 자신의 스냅샷 밖에서 변경됐음을 감지합니다. 아래 시간표는 이 흐름을 손으로 추적한 예이며 실제 SQL Server에서 실행하지 않았습니다.

같은 행의 커밋 순서를 따라 본 SNAPSHOT update conflict
시점T1: SNAPSHOTT2: READ COMMITTED판정
초기재고 10을 읽을 수 있음-두 트랜잭션이 같은 행을 대상으로 합니다.
T1 읽기 뒤스냅샷 기준 값 10을 계속 봄재고를 8로 UPDATET2의 변경은 커밋 전까지 다른 일반 읽기에 보이지 않습니다.
T2 커밋여전히 오래된 버전 10을 읽음재고 8 커밋T1의 읽기 값과 현재 커밋 행이 달라졌습니다.
T1 갱신 시도같은 행 수정에서 update conflict, T1 종료·롤백커밋된 값 8 유지기존 10을 바탕으로 쓴 9가 조용히 덮어쓰지 않습니다.

핵심은 T1의 계산 결과 9가 '정답으로 저장된다'고 가정하지 않는 것입니다. T2가 먼저 커밋한 상태에서 T1이 충돌하면 T1 트랜잭션 전체가 오류로 종료되고 롤백됩니다. Microsoft의 공식 예제는 같은 행을 먼저 조회한 SNAPSHOT 트랜잭션보다 다른 연결이 먼저 변경을 커밋한 뒤, 이전 트랜잭션이 다시 그 행을 수정할 때 오류 3960이 나고 롤백되는 순서를 보여 줍니다.

설정 전제와 재시도 책임을 분리합니다

SNAPSHOT 격리를 사용하기 전에 해당 데이터베이스에서 ALLOW_SNAPSHOT_ISOLATION 옵션을 ON으로 설정해야 합니다. 이 옵션은 읽기 일관성을 위한 행 버전 사용을 허용하는 데이터베이스 설정입니다. READ_COMMITTED_SNAPSHOT과 혼동하지 마세요. 이름에 Snapshot이 들어가지만 RCSI는 READ COMMITTED 문장별 읽기를 행 버전으로 바꾸고, SNAPSHOT은 트랜잭션 전체의 읽기 시점을 유지합니다.

충돌 뒤에는 현재 트랜잭션 안에서 같은 UPDATE만 무조건 반복하지 않습니다. 종료된 트랜잭션을 새로 시작하고, 최신 상태와 업무 조건을 다시 읽은 다음 요청을 적용할지 판단해야 합니다. 예를 들어 '재고를 1 줄여 9로 저장'처럼 낡은 값을 덮어쓰는 것보다 '현재 재고가 1 이상이면 1을 차감'이라는 조건이 유지되는지 확인하는 절차가 안전합니다. 자동 재시도는 새 경쟁을 다시 만들 수 있으므로 횟수 제한과 사용자에게 돌려줄 결과도 응용프로그램이 정해야 합니다.

행 버전 읽기와 쓰기 잠금은 같은 말이 아닙니다

  1. ALLOW_SNAPSHOT_ISOLATION이 대상 데이터베이스에서 ON인지 확인합니다.
  2. T1의 첫 읽기 시점과 대상 행의 키를 기록합니다.
  3. T2가 같은 키를 수정하고 커밋했는지 시간순으로 놓습니다.
  4. T1이 같은 행을 쓰는 순간 외부 변경이 확인되면 충돌로 종료되는지 판정합니다.
  5. 재시도는 새 트랜잭션에서 최신 값을 다시 읽고 업무 조건을 검증한 뒤 수행합니다.

흔한 오해는 SNAPSHOT이 읽기 잠금을 덜 쓰므로 모든 쓰기 충돌도 자동으로 해결한다고 생각하는 것입니다. 행 버전은 읽기에 일관된 과거 값을 제공하지만, 그것만으로 두 수정의 업무 의미를 합치지 않습니다. SNAPSHOT의 update conflict는 별도 사건이며 문서는 애플리케이션이 충돌을 처리하고 재시도해야 한다고 안내합니다. 반대로 단순히 T2가 다른 행을 수정했다면 이 예시의 같은 행 충돌과 같다고 단정할 수 없습니다. 대상 행과 커밋 순서를 확인하세요.

정답을 가리고 충돌 발생 시점을 설명해 보세요

T1이 SNAPSHOT에서 계좌 잔액 100을 읽었고, T2가 같은 계좌를 80으로 바꾸어 커밋했습니다. 그 뒤 T1이 잔액을 90으로 수정하려고 합니다. 이 순서에서 90은 자동으로 저장되지 않습니다. 같은 행이 T1의 스냅샷 뒤에 바뀌었으므로 update conflict가 발생해 T1이 종료되고 롤백될 수 있습니다. 애플리케이션은 새 트랜잭션으로 최신 80을 읽고 업무 규칙에 따라 처리해야 합니다.

정답을 가리고 확인해 보세요

이 글을 위해 만든 학습 예시이며 공식 기출문제나 판매 카드의 인용이 아닙니다.

T1이 SNAPSHOT에서 행을 읽은 뒤 T2가 같은 행을 수정하고 커밋했습니다. 그 후 T1이 해당 행을 변경하면 낡은 값이 반드시 저장되나요?

정답과 해설 확인아닙니다. SQL Server는 update conflict를 발생시키고 T1 SNAPSHOT 트랜잭션을 종료·롤백할 수 있습니다.

SNAPSHOT은 트랜잭션 단위 읽기 버전을 제공하지만, 스냅샷 뒤 바뀐 같은 행에 대한 수정 충돌을 조용히 덮어쓰지 않습니다. 애플리케이션은 새 트랜잭션에서 최신 상태를 다시 확인해 재시도해야 합니다.