트랜잭션 관리 수준,, 이게 뭔 말일까
트랜잭션은 ACID 특성을 가진 데이터베이스의 주요 작동 요소이다.
이를 관리하는 수준이 과연 뭘까.
한번 알아보자
트랜잭션 관리 수준은 데이터베이스에서 동시에 여러 트랜잭션이 실행될 때 발생할 수 있는 데이터 불일치 문제를 방지하기 위해 설정하는 격리 수준이다. 각 트랜잭션이 다른 트랜잭션의 영향을 얼마나 받을지를 정의하며, 네 가지 주요 격리 수준이 있다.
아하. 동시에 여러 트랜잭션이 일어나면 Consistency, Isolation 이 위배될 수 있다.
이것을 방지하기 위해 개발자는 트랜잭션의 격리 수준을 설정해야 한다.
이러한 네가지 격리 수준이 존재한다. 격리 수준이 올라갈 수록 데이터 일치는 보장되지만 그만큼 성능이 저하되는 트레이드 오프 관계에 놓인다.
솔직히 읽기만 하면 와닿지가 않는다. 격리수준을 직접 실습하면서 알아보자.
환경 설정
- 두 개의 MySQL 클라이언트 세션을 사용하여 실습을 진행한다.
- 각 세션은 하나의 트랜잭션을 시작하고, 격리수준을 다르게 설정하여 동일한 데이터를 읽고 수행하는 실습을 진행한다.
Read Uncommitted 수준에서는 트랜잭션이 커밋되지 않은 데이터를 다른 트랜잭션에서 읽을 수 있다. 이로 인해 Dirty Read가 발생할 수 있다.
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
START TRANSACTION;
UPDATE accounts SET balance = 5000 WHERE id = 1;
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
START TRANSACTION;
SELECT balance FROM account WHERE id = 1;
여기서 세션 2는 커밋되지 않은 balance 5000값을 읽을 수 있다.
ROLLBACK;
세션 1이 롤백한다면, 세션 2는 비정상적인 데이터를 읽은 셈이 된다.
이를 통해 트랜잭션이 커밋되지 않더라도 다른 트랜잭션에서 해당 데이터를 읽을 수 있는 Dirty Read문제를 확인할 수 있다.
Read Committed 수준에서는 트랜잭션이 커밋된 데이터만 읽을 수 있다. 이로 인해 Dirty Read는 방지되지만, Non-repeatable Read가 발생할 수 있다.
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
START TRANSACTION;
UPDATE accounts SET balance = 7000 WHERE id = 1;
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
START TRANSACTION;
SELECT balance FROM accounts WHERE id = 1;
여기서 세션 2는 기존 값을 읽게된다.
COMMIT;
SELECT balance FROM accounts WHERE id = 1;
변경된 balance 7000값을 읽어오게 된다.
세션 2가 두번의 SELECT에서 다른 값을 읽는 현상을 통해 Non-repeatable Read문제를 확인할 수 있다.
곧, 트랜잭션 내에서 데이터를 여러 번 읽을 때, 그 값이 항상 동일해야 하는 기대를 져버리게 된다.
데이터 무결성이 손상되고 비즈니스 로직에 혼란이 발생할 문제가 있다.
Reapeatable Read수준에서는 트랜잭션이 시작될 때 읽은 데이터가 이후 트랜잭션이 끝날 때까지 동일하게 유지된다. 하지만 Phantom Read는 여전히 발생할 수 있다.
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
START TRANSACTION;
SELECT balance FROM account WHERE id = 1;
-- 5000을 읽어온다.
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
START TRANSACTION;
UPDATE accounts SET balance = 6000 WHERE id = 1;
COMMIT;
SELECT balance FROM accounts WHERE id = 1;
COMMIT;
세션 2가 값을 수정했음에도 불구하고, 여전히 5000을 읽어온다. Non-Repeatable Read가 방지된다.
세션 1이 트랜잭션 내에서 동일한 값을 계속해서 읽을 수 있는 것을 확인할 수 있다.
하지만 중간에 Insert가 일어난다고 할때, 트랜잭션이 처음 데이터를 조회했을 때와 나중에 같은 쿼리를 실행 했을 때, 행의 개수가 달라지는 문제가 발생할 수 있다.
즉, 트랜잭션 내에서 동일한 조건으로 여러 번 데이터를 읽을 때, 중간에 다른 트랜잭션이 새로운 행을 삽입하거나 삭제함으로써 결과가 달라지는 Phantom Read 현상이 발생할 수 있다.
단순히 수정된 데이터 조회의 결괏값이 달라지는 문제뿐만 아니라 삭제, 삽입에 대해서도 일관성을 기대하는 것이다.
Serializable 수준에서는 트랜잭션이 완전히 격리되어 실행되므로 Dirty Read, Non-repeatable Read, Phantom Read 모두 방지된다.
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
START TRANSACTION;
SELECT balance FROM account WHERE id = 1;
-- 5000을 읽어온다.
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
START TRANSACTION;
UPDATE accounts SET balance = 6000 WHERE id = 1;
세션 1이 끝나기 전까지 대기해야 한다.
COMMIT;
COMMIT;
실습을 통해 어떤 식으로 격리수준이 관리되며, 발생할 수 있는 문제를 중심으로 살펴봤다.
앞으로 트랜잭션 격리수준을 접하게 되면, 왜 해당 트랜잭션 격리수준을 선택했는지, 성능, 일관성과의 트레이드 오프를 어떤 식으로 처리했는지를 살펴볼 수 있겠다.