직렬성 격리는 데이터베이스가 여러 트랜잭션들이 직렬적으로 실행되는 것즉 동시성 없이 한 번에 트랜잭션 하나만 실행)과 동일한 결과가 나오도록 보장한다는 것을 의미
하지만, 현실적으로 직렬성 격리를 달성하는 것은 간단한 일이 아님. 달성한다고 해도 직렬성 격리는 성능 비용이 있고 많은 데이터를 다루어야 한다면 그 비용을 지불하지 않는 선택을 내릴 수 있음
대부분의 데이터베이스는 직렬성 격리보다 완화된(비직렬성) 격리성을 사용
가장 기본적인 수준의 트랜잭션 격리. 아래와 같은 두 가지를 보장
더티 읽기
트랜잭션이 데이터베이스에 데이터를 썼지만, 아직 커밋되지는 않은 상황에서 다른 트랜잭션에서 커밋되지 않은 데이터를 볼 수 있다면 이를 더티 읽기라고 함
즉, 트랜잭션이 쓴 내용은 커밋된 후에야 다른 트랜잭션에서 보일 수 있다는 의미
더티 읽기를 막아야 하는 이유
더티 쓰기
두 트랜잭션이 동일한 객체를 동시에 갱신한다고 가정. 일반적으로 나중에 쓴 내용이 먼저 쓴 내용을 덮어씀
하지만 먼저 쓴 내용이 아직 커밋되지 않은 트랜잭션에서 쓴 내용이며, 나중에 실행된 쓰기 작업이 커밋되지 않은 값을 덮어쓴다면, 순서가 보장되지 않음
보통 먼저 쓴 트랜잭션이 커밋되거나 어보트될 때까지 두 번째 쓰기를 지연시키는 방법으로 더티 쓰기를 방지
더티 쓰기를 방지함으로서 몇 가지 동시성 문제를 해결할 수는 있지만, 아래 그림과 같은 경쟁 조건은 막지 못함. 이와 같은 동시성 문제를 해결하는 방법으로 갱신 손실 방지가 있음
구현
로우 수준 잠금
트랜잭션에서 특정 객체(로우나 문서)를 변경하고 싶다면 먼저 해당 객체에 대한 잠금을 획득
트랜잭션이 커밋되거나 어보트될 때까지 잠금을 보유
다른 트랜잭션에서 동일한 객체에 쓰기를 원한다면 첫 번째 트랜잭션이 커밋되거나 어보트된 후에야 잠금을 얻어 진행
오라클 11g, PostgreSQL, MemSQL 등이 기본 설정으로 사용
커밋 후 읽기 격리를 사용하더라도 동시성 버그가 완전히 해소되지는 않음
엘리스는 두 개의 계좌에 각각 500씩 가지고 있고, 하나의 계좌에서 다른 계좌로 100을 송금
운이 없어서 트랜잭션이 처리되고 있는 순간에 계좌 잔고를 본다면, 총액이 900이라고 나올 수 있음
이런 현상을 비반복 읽기 or 읽기 스큐(read skew)(시간적인 이상 현상을 뜻함)라고 함
읽기 스큐는 커밋 후 읽기 격리에서는 받아들일 수 있는 것으로 여겨짐
물론 이런 현상은 일시적일테지만, 몇몇 경우에서는 이를 받아들일 수 없기도 함
스냅숏 격리는 각 트랜잭션이 데이터베이스의 일관된 스냅숏으로부터 데이터를 읽게 함으로써 이를 해결
PostgreSQL, InnoDB(MySQL, Oracle) 등에서 지원
구현
커밋 후 읽기에서처럼 쓰기를 실행하는 트랜잭션은 같은 객체에 쓰는 다른 트랜잭션을 차단하지만, 읽을 때는 차단하지 않음
성능 관점에서 스냅숏 격리의 핵심원리는 읽는 쪽에서 쓰는 쪽을 결코 차단하지 않고 쓰는 쪽에서 읽는 쪽을 결코 차단하지 않는다는 것
데이터베이스는 객체마다 커밋된 버전 여러 개를 유지. 이러한 기법을 **다중 버전 동시성 제어(Multi-Version Concurrency Control, MVCC)라고 함
MVCC는 커밋 후 읽기에서도 사용됨(버전을 두 개는 가지고 있어야 하기 때문)
색인
스냅숏 격리에서 색인은 어떻게 저장할까?
간단한 예는 색인이 객체의 모든 버전을 가리키게 하고 색인 질의가 현재 트랜잭션에서 볼 수 없는 버전을 걸러내게 하는 것
PostgreSQL은 동일한 객체의 다른 버전들이 같은 페이지에 저장될 수 있다면, 색인 갱신을 회피하는 최적화를 수행
카우치DB 등은 추가 전용이며 쓸 때 복사되는 변종 B트리를 사용. 페이지가 갱신될 때 덮어쓰는 대신 각 변경된 페이지의 새로운 복사본을 생성
스냅숏 격리의 여러 이름들