[DB] 직렬성 격리
스냅숏 격리와 같은 완화된 격리 수준은 일부 이상 현상에 노출되기 쉽다.
갱신손실 (lost update)
두 클라이언트가 동시에 read modify write 주기를 실행한다. 한 트랜잭션이 다른 트랜잭션의 변경을 포함하지 않은 채로 다른 트랜잭션이 쓴 내용을 덮어써서 데이터가 손실된다. 스냅숏 격리 구현 중 어떤 것은 이런 이상 현상을 자동으로 막아주지만 그렇지 않은 것은 수동 잠금 (SELECT FOR UPDATE)이 필요하다.
쓰기 스큐 (write skew)
트랜잭션이 무언가를 읽고 읽은 값을 기반으로 어떤 결정을 하고 그 결정을 데이터베이스에 쓴다. 그러나 쓰기를 실행하는 시점에는 결정의 전제가 더 이상 참이 아니다.
팬텀 읽기
트랜잭션이 어떤 검색 조건에 부합하는 객체를 읽는다. 다른 클라이언트가 그 검색 결과에 영향을 주는 쓰기를 실행한다. 스냅숏 격리는 간단한 팬텀 읽기는 막아주지만 쓰기 스큐 맥락에서 발생하는 팬텀은 색인 범위 잠금처럼 특별한 처리가 필요하다.
직렬성 격리는 위 모든 문제들로부터 보호해줄 격리 수준이다. 보통 가장 강력한 격리 수준이라고 여겨진다. 여러 트랜잭션이 병렬로 실행되더라도 최종 결과는 동시성 없이 한 번에 하나씩 직렬로 실행될 때와 같도록 보장한다. 아래는 직렬성 격리에 대한 세 가지 기법이다.
말 그대로 트랜잭션을 순서대로 실행하기
말 그대로 동시성을 완전히 제거하는 것이다. 한 번에 트랜잭션 하나씩만 직렬로 단일 스레드에서 실행하면 된다. 트랜잭션의 실행 시간이 아주 짧고 트랜잭션 처리량이 단일 CPU 코어에서 처리할 수 있을 정도로 트랜잭션 처리량이 낮다면 아주 간단하고 효과적인 선택이다.
2단계 잠금 (2PL)
비관적 동시성 제어 메커니즘이다. 잠금(lock)을 이용한 방법이다.
잠금(lock)은 공유 모드(shared mode)와 독점 모드(exclusive mode)로 이용될 수 있다.
- 트랜잭션이 객체를 읽기 원한다면 공유 모드로 잠금을 획득해야 한다.
- 트랜잭션이 객체에 쓰기를 원한다면 독점 모드로 잠금을 획득해야 한다.
동시에 여러 트랜잭션이 공유 모드로 잠금을 획득하는 것은 되지만 만일 독점 모드로 잠금을 획득한 트랜잭션이 있으면 트랜잭션이 완료될 때까지 기다려야 한다.
트랜잭션이 잠금을 획득한 후에는 트랜잭션이 종료 (커밋 또는 어보트)될 때까지 잠금을 가지고 있어야 한다. 그래서 2 phase 이다. 첫 번째 phase는 잠금을 획득할 때이고 두 번째는 잠금을 해제할 때이다.
색인 범위 잠금 (index-range locking)
트랜잭션이 특정 범위의 인덱스를 잠근다. 다른 트랜잭션이 동일한 범위에 있는 데이터를 수정하는 것을 방지한다. 이는 팬텀과 쓰기 스큐로부터 보호해주는 효과를 낳는다.
직렬성 스냅숏 격리 (serializable snapshor isolation, SSI)
낙관적 동시성 제어 메커니즘이다. 스냅숏 격리를 기반으로 한다.
기존의 스냅숏 격리에서 트랜잭션은 어떤 전제(트랜잭션이 시작할 때 참이었던 사실, 예를 들어 "현재 두 명의 의사가 호출 대기 중이다.") 를 기반으로 어떤 동작을 한다. 나중에 해당 트랜잭션이 커밋하려고 할 때 원래 데이터가 바뀌어서 그 전제가 더 이상 참이 아닐 수 있다.
뒤처진 전제를 기반으로 동작하는 상황을 감지하고 트랜잭션을 어보트시켜야 한다.