동시성 제어
만약 여러 사용자가 동시에 데이터베이스에 접근하는 상황에서 적절한 통제가 이루어지지 않는다면, 데이터베이스의 무결성이 깨져 의도하지 않은 결과가 반환되는 문제가 발생할 수 있다. 따라서 DBMS에서는 동시성 제어 (Concurrency Control)를 통해 데이터베이스의 무결성을 보호하고, 트랜잭션이 항상 정확하고 일관된 데이터를 참조할 수 있도록 조정한다.
동시성 제어의 목표는 트랜잭션이 동시에 실행될 수 있도록 허용하면서도 데이터의 일관성과 무결성을 유지하는 것이다.
동시성 제어 기법
1. Locking
Locking은 공유 자원에 대한 동시 엑세스를 제어하는 전통적인 방법이다. 잠금을 획득한 스레드만 공유 자원(Critical Section)에 접근할 수 있어 신뢰성과 안정성이 높다. 하지만, 동시 처리 속도가 저하될 수 있고 대기 시간이 발생할 수 있다. 잠금에는 크게 읽기 잠금(Shared Lock, S-Lock)과 쓰기 잠금(Exclusive Lock, X-Lock)이 있다.
읽기 잠금은 공유 잠금이라고도 하며 읽기 작업을 수행할 때 설정된다. S-Lock이 설정된 상태에서는 다른 트랜잭션도 동일한 데이터 항목에 대해 읽기 잠금을 설정할 수 있다. 하지만 잠금을 설정한 데이터 항목에 대해 데이터를 변경하는 것은 불가능하다. 즉, 읽기 연산(Read)만 가능하다.
쓰기 잠금은 배타 잠금이라고도 하며 주로 데이터를 변경하는 작업을 수행할 때 사용한다. 쓰기 잠금을 획득한 트랜잭션은 해당 레코드에 대해 읽기 연산(Read)와 쓰기 연산(Write)이 모두 가능하지만, 잠금을 획득하지 못한 트랜잭션은 해당 잠금이 반환되기 전까지 읽기 및 쓰기 연산 모두 수행할 수 없다. 쓰기 잠금은 하나의 트랜잭션만 독점적으로 접근이 가능하다. 즉, 하나의 레코드에 대해 동시에 여러 개의 쓰기 잠금을 설정할 수 없다.
2. MVCC(Multi Version Concurrency Control)
MVCC는 원본의 데이터와 변경 중인 데이터를 동시에 유지하는 방식으로, 원본 데이터에 대한 Snapshot을 백업하여 보관한다. DBMS마다 이를 구현하는 세부적인 방식이 다른데, MySQL의 InnoDB에서는 언두 로그(Undo log)를 이용하여 이 기능을 구현한다.
member 테이블에 한 건의 레코드를 INSERT하는 구문을 실행했을 때의, 데이터베이스의 상태는 위와 같은 상태이다. 여기서 해당 데이터를 변경하는 UPDATE 구문을 실행하면, 아래와 같이 상태가 변경된다.
UPDATE 문장이 실행되면, 커밋 실행 여부와 관계없이 InnoDB의 버퍼 풀은 새로운 값인 '경기'로 업데이트된다. 디스크의 데이터 파일은 InnoDB 스토리지 엔진의 백그라운드 스레드에 의해서 변경 내용이 기록되기 때문에 기록 시점에 따라 값이 업데이트 되어 있을 수도 있고 아닐 수도 있어 '?'로 표기하였다. 해당 상태에서 id가 42인 데이터를 조회하는 SELECT 쿼리를 실행한다면 어디를 조회할까?
이는 격리 수준에 따라 다르다. READ_COMMITED 이상의 격리 수준에서는 아직 해당 트랜잭션이 커밋되지 않았기 때문에, 변경되기 이전의 내용을 보관하고 있는 언두 영역의 데이터를 반환한다. 즉, 하나의 레코드에 대해 여러 개의 버전이 유지되고, 필요에 따라 어느 데이터가 보여지는지 여러 상황에 따라 달라지는 구조이다.
이러한 과정을 DBMS에서는 MVCC라고 표현한다. 해당 상태에서 트랜잭션이 커밋되면, 지금의 상태를 영구적인 데이터로 만들어 버리고 이 언두 영역을 필요로 하는 트랜잭션이 더 없다면 삭제시킨다. 반대로 롤백이 된다면 언두 영역에 있는 데이터를 버퍼 풀로 다시 복구하고, 언두 영역의 내용을 삭제한다.
잠금 없는 일관된 읽기(Non-Locking Consistent Read)
MVCC의 가장 큰 목적은 잠금(Lock)을 사용하지 않는 일관된 읽기를 제공하는데 있다. InnoDB 스토리지 엔진 또한 이 MVCC 기술을 이용하여 잠금을 걸지 않고 읽기 작업을 수행한다. 때문에 격리 수준이 SERIALIZABLE이 아닌 격리 수준에서는 INSERT와 연결되지 않은 순수한 읽기(SELECT) 작업은 다른 트랜잭션의 변경 작업과 관계없이 항상 잠금을 대기하지 않고 바로 실행된다.
트랜잭션B가 레코드를 변경하고 커밋을 수행하지 않은 상태라고 하더라도, 이는 트랜잭션A의 SELECT 작업을 방해하지 않는다. 트랜잭션A는 데이터의 변경 작업과 관계없이 변경되기 전의 데이터를 읽기 위해 언두 로그를 사용하면 되기 때문이다. 이를 '잠금 없는 일관된 읽기'라고 표현한다.
MVCC 모델은 Lock을 걸지 않기 때문에 매우 빠르게 동작한다는 이점이 있다. 하지만 MVCC 모델은 하나의 데이터에 대한 여러 버전의 데이터를 허용하기 때문에 데이터 버전 충돌이 발생할 수도 있으며 백업 데이터가 너무 많이 쌓여 처리 성능이 저하되는 문제점이 존재할 수 있다.