여러 트랜잭션을 동시에 돌리면서도 혼자 돌린 것과 같은 결과를 내게 만드는 일. ACID의 격리성이 이것을 요구하고, 동시성 제어가 그것을 구현한다.
사라지는 갱신
J가 H에게 20만원을 이체하는 동안, 하필 H도 ATM에서 자기 계좌에 30만원을 입금한다고 하자.
두 트랜잭션이 모두 H의 잔고를 읽고, 계산하고, 쓴다. 읽는 시점과 쓰는 시점 사이에 상대가 끼어들면 나중에 쓴 쪽이 먼저 쓴 쪽의 결과를 덮어버린다. 20만원이나 30만원 중 하나가 증발한다. 갱신한 데이터가 이렇게 사라지는 현상을 Lost Update라고 한다.
순차와 비순차 스케줄
여러 트랜잭션이 동시에 실행될 때 각 트랜잭션에 속한 연산들의 실행 순서를 스케줄이라고 한다. 한 트랜잭션 내부의 순서는 바뀌지 않는다. 연산은 r1(H)처럼 적는다. 읽기(r) 또는 쓰기(w), 트랜잭션 번호, 대상 데이터 순이다.
트랜잭션이 겹치지 않고 하나씩 실행되는 순차 스케줄은 결과가 항상 옳다. 대신 IO를 하는 동안 CPU가 놀고 한 번에 하나만 돌므로 현실적으로 쓸 수 없다. 겹쳐서 실행하는 비순차 스케줄은 동시성이 높아 같은 시간에 더 많은 트랜잭션을 처리하지만, 겹치는 방식에 따라 이상한 결과가 나온다. 성능을 위해 겹쳐 실행하고 싶지만 이상한 결과는 원하지 않는다는 것이 문제의 모양이다.
여기서 해법의 아이디어가 나온다. 순차 스케줄과 동일한 비순차 스케줄만 실행하는 것이다. 겹쳐 돌리되 그 결과가 하나씩 돌린 것과 같다는 보장이 있으면 된다. 그러려면 “동일하다”를 먼저 정의해야 한다.
충돌 직렬 가능성
정의의 재료가 충돌이다. 두 연산이 서로 다른 트랜잭션에 속하고, 같은 데이터에 접근하고, 적어도 하나가 쓰기이면 충돌한다. 충돌이 중요한 이유는 충돌하는 두 연산의 순서가 바뀌면 결과가 바뀌기 때문이다. 뒤집어 말하면 충돌하지 않는 연산끼리는 순서를 바꿔도 결과가 같다.
두 스케줄이 같은 트랜잭션들로 이루어져 있고 모든 충돌 연산 쌍의 순서가 양쪽에서 같으면 충돌 동등이라고 한다. 충돌하는 것들의 순서만 같으면 나머지가 어떻게 섞여 있든 결과가 같다. 어떤 비순차 스케줄이 어떤 순차 스케줄과 충돌 동등하면 그 스케줄을 충돌 직렬 가능하다고 한다. 겹쳐 돌렸지만 하나씩 돌린 것과 결과가 같다는 보장이다. 앞의 이체 예에서 이상한 결과가 나온 스케줄은 가능한 어떤 순차 스케줄과도 충돌 동등하지 않았다.
DBMS가 실제로 하는 일
DBMS의 동시성 제어가 하는 일이 정확히 이것이다. 다만 실행해놓고 나서 직렬 가능한지 검사하지는 않는다. 요청이 몰리면 동시에 도는 트랜잭션이 너무 많아 검사 비용을 감당할 수 없다. 애초에 직렬 가능한 스케줄만 나오도록 프로토콜로 실행 순서를 통제한다.
엄격함의 대가
직렬 가능성을 엄격히 지키면 안전하지만 느리다. 트랜잭션끼리 서로 기다리는 시간이 길어지기 때문이다. 그래서 DBMS는 어느 정도까지 격리할지를 고를 수 있게 해둔다. 그것이 격리 수준이다.