트랜잭션의 ACID 속성 중 격리성(Isolation)이 보장되지 않을 때 발생할 수 있는 문제점
트랜잭션의 ACID 속성 중 격리성(Isolation)에 대해서 간단하게 설명하고, 보장되지 않았을 때 발생할 수 있는 문제점과 이를 해결하기 위한 트랜잭션 격리 수준들을 제시합니다.
트랜잭션과 트랜잭션의 속성, 그리고 격리성(Isolation)
트랜잭션은 데이터베이스에서 여러 작업을 하나의 단위로 묶어 처리하는 개념입니다. 트랜잭션의 속성 ACID는 원자성(Atomicity), 일관성(Consistency), 격리성(Isolation), 지속성(Durability)을 의미하고, 이 속성을 지키는 것은 트랜잭션의 신뢰성을 높입니다.
그중 격리성(Isolation)은 동시에 실행되는 트랜잭션이 서로 간섭하지 않도록 보장하는 것으로 트랜잭션이 독립적으로 실행되도록 해 데이터의 일관성을 유지하도록 합니다.
ACID 속성 중 격리성이 보장되지 않았을 때의 문제점
트랜잭션에서 격리성이 보장되지 않는다는 건, 동시에 실행되는 트랜잭션이 서로 간섭할 수 있게 된다는 것입니다. 실행되는 트랜잭션이 서로에게 간섭할 때 다음과 같은 문제 세 가지가 발생합니다.
1. Dirty Read
한 트랜잭션이 아직 커밋되지 않은 다른 트랜잭션의 변경사항(UPDATE) 을 읽게 되는 현상
-- 트랜잭션 A
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE iD = 1;
-- A COMMIT 하지 않음
-- 트랜잭션 B (아직 커밋되지 않은 트랜잭션 A의 변경사항인 데이터를 읽었다. 즉, Dirty Read 발생)
SELECT balance FROM accounts WHERE id = 1; -- -100 적용된 값을 조회한다
-- 트랜잭션 A 롤백
ROLLBACK; -- 트랜잭션 B가 읽은 값이 잘못된 데이터가 되어버린다.
2. Non-Repeatable Read
하나의 트랜잭션 내에서 동일한 데이터를 두 번 조회했을 때 그 값이 다르게 나타나는 현상
-- 트랜잭션 A (READ COMMITTED 격리 수준)
BEGIN;
SELECT balance FROM accounts WHERE id = 1; -- 결과 : 100
-- 트랜잭션 B
BEGIN;
UPDATE accounts SET balance = 200 WHERE id = 1;
COMMIT;
-- 트랜잭션 A (하나의 트랜잭션 내에서 동일한 데이터 두 번 조회했는데 값이 달라졌다. 즉, Non-repeatable Read 발생)
SELECT balance FROM accoutns WHERE id = 1; -- 결과 : 200
COMMIT;
3. Phantom Read
한 트랜잭션 내에서 동일한 쿼리를 실행했을 때 이전에 없던 레코드가 나타나거나 존재하던 레코드가 사라지는 현상(INSERT, DELETE)
-- 트랜잭션 A
BEGIN;
SELECT * FROM orders WHERE status = 'PENDING'; -- 결과 5개
-- 트랜잭션 B
BEGIN;
INSERT INTO orders (id, status) VALUES (101, 'PENDING');
-- 트랜잭션 A
SELECT * FROM orders WHERE status = 'PENDING'; -- 결과 6개
COMMIT;
트랜잭션 격리 수준과 문제점의 방지
- READ UNCOMMITTED
다른 트랜잭션의 미완료 변경 사항을 읽을 수 있다. - READ COMMITTED
다른 트랜잭션이 COMMIT한 데이터만 읽을 수 있다. → Dirty Read 방지 - REPEATABLE READ
트랜잭션 내에서 같은 데이터를 여러 번 조회해도 값이 변하지 않는다 → Dirty Read, Non-Repeatable Read 방지 - SERIALIZABLE
모든 트랜잭션을 직렬화하여 실행한다. → Dirty Read, Non-Repeatable Read, Phantom Read 방지
READ UNCOMMITTED는 실무에서 거의 쓰지 않는 격리 수준으로, PostgreSQL은 READ UNCOMMITTED수준을 지원하지 않고 있습니다. SERIALIZABLE 또한 동시 처리량 급감, 데드락의 가능성이 매우 높아 잘 이용되지 않습니다.
대부분의 DBMS가 READ COMMITTED를 기본 격리 수준으로 채택하고 있습니다. Dirty Read를 방지하고, 성능적인 면에서 적잘하다 여기기 때문입니다.
SERIALIZABLE 격리 수준을 잘 이용하지 않는다면, Phantom Read 문제는 어떻게 될까요?
Phantom Read 는 범위 쿼리를 이용하면서, 동시에 다른 트랜잭션이 해당 범위에 데이터를 추가/삭제하는 경우에 발생하고, 모든 쿼리에서 발생하는 문제가 아니며, 실제 문제가 되지 않는 경우가 많습니다. Phantom Read가 문제가 될 수 있는 중요한 트랜잭션의 경우 애플리케이션 로직에서 이를 관리하거나 특정 트랜잭션에 대해서만 더 높은 격리 수준을 사용합니다.