Transaction과 격리 수준

세정·2024년 2월 15일

Transaction
데이터베이스 시스템에서 수행되는 작업의 논리적 단위.

트랜잭션의 개념과 특징

트랜잭션은 데이터베이스의 일련의 연산들이 완전하게 실행되거나 전혀 실행되지 않아야 함을 말한다. 예를 들어, 여러 사용자가 동시에 데이터베이스에 접근하여 데이터를 읽거나 수정할 수 있는 환경에서, 트랜잭션은 데이터베이스의 일관성과 무결성을 유지하는 데 핵심적인 역할을 한다. 이를 통해 데이터의 신뢰성과 정확성을 보장할 수 있다. 트랜잭션 관리는 데이터베이스 관리 시스템(DBMS)의 핵심 기능 중 하나이며, ACID 특성은 다음과 같다.

원자성 (Atomicity)

트랜잭션 내의 모든 연산들은 하나의 원자적인 단위로 간주되어야 한다. 트랜잭션 내의 모든 연산들이 성공적으로 수행되면, 트랜잭션은 완전하게 실행된 것으로 간주한다. 하나라도 실패하면 모든 연산은 취소되고 롤백된다.

일관성 (Consistency)

데이터베이스는 트랜잭션이 실행되기 전과 후, 수행 도중을 포함하여 항상 일관된 상태를 유지해야 한다. 즉, 트랜잭션이 데이터베이스를 유효한 상태로 유지해야 한다. 예를 들어, 두 개의 관련된 데이터를 업데이트할 때, 이러한 업데이트는 동시에 반영되어야 하며, 데이터 사이의 불일치가 발생하지 않아야 하는 것이다. 이는 트랜잭션에 대한 로그를 남겨야한다는 것을 의미한다.

격리성 (Isolation)

여러 트랜잭션이 동시에 실행될 때, 각 트랜잭션은 다른 트랜잭션의 작업에 영향을 받지 않아야 한다. 즉, 한 트랜잭션의 결과가 다른 트랜잭션에게는 완전하게 감춰져 있어야 하는 것이다. 이를 통해 각 트랜잭션은 마치 시스템에서 독립적으로 실행되는 것처럼 동작하게 된다.

지속성 (Durability)

트랜잭션이 성공적으로 완료되면 해당 트랜잭션에 대한 변경사항은 영구적으로 데이터베이스에 반영되어야 한다. 시스템 장애를 포함한 외부적 문제가 발생해도 변경 사항에 변함이 없어야 한다.


격리 수준 (Isolation Level)

트랜잭션이 다른 트랜잭션으로부터 얼마나 격리되어 있는가를 나타내는 개념.

격리 수준은 크게 네 가지로 나뉘며, 아래로 갈수록 높아진다. 종종 더 높은 격리 수준은 고립성(안정성)을 제공하여 데이터의 일관성과 무결성을 유지하기 위해 선택될 수 있지만, 성능 저하를 유발할 수 있다.

1. UNREAD COMMITTED (미완료 읽기)

가장 낮은 격리 수준이다. 하나의 트랜잭션을 변경하는 동안, 다른 트랜잭션이 아직 커밋되지 않은 데이터를 읽을 수 있다. 이는 Dirty Read, Non-Repeatable Read, Phantom Read 등의 문제가 발생할 수 있다.

Read phenomena (읽기 현상)
트랜잭션 격리 수준에서 발생할 수 있는 세 가지 주요 현상.

Dirty Read
한 트랜잭션이 아직 커밋되지 않은 다른 트랜잭션의 변경 사항을 읽는 경우.

Non-Repeatable Read
한 트랜잭션이 동일 쿼리를 실행했을 때 다른 결과가 발생하는 경우.

Phantom Read
한 트랜잭션이 동일 쿼리를 실행했을 때 새롭게 레코드가 추가 혹은 삭제되는 경우.

관련한 예시를 들어보겠다.

-- 사용자 1의 트랜잭션
BEGIN TRANSACTION;
UPDATE User SET age = 24 WHERE user_id = '123';
-- 특정 회원의 나이를 24로 변경

-- 사용자 2의 트랜잭션
BEGIN TRANSACTION;
SELECT age FROM User WHERE user_id = '123';
-- 커밋되지 않은 상태이기 때문에 사용자 2의 쿼리에서 변경되지 않은 결과를 반환 (Dirty Read)

사용자 1user_id123인 사용자의 나이를 24로 변경하는 UPDATE 쿼리를 실행하였다. 하지만 이 변경 사항은 아직 커밋되지 않은 상태이다. 이 상태에서 사용자 2가 동일 사용자의 나이를 조회하는 쿼리를 실행하였다. 이러한 상황에서 사용자 2는 변경되지 않은 나이를 조회할 위험이 있다. (Dirty Read)

2. READ COMMITTED (완료 읽기)

커밋된 데이터만 읽을 수 있으며, 다른 트랜잭션이 데이터를 변경하면 해당 변경 사항이 바로 반영된다. 한 트랜잭션 내에서 여러 번 같은 쿼리를 실행하더라도, 각 실행 결과가 일관성을 보장하지 않는다. 이는 Dirty Read는 방지하지만, Non-Repeatable Read와 Phantom Read가 발생할 수 있다.

관련한 예시를 들어보겠다.

-- 사용자 1의 트랜잭션
BEGIN TRANSACTION;
SELECT * FROM User WHERE membership = 'Gold';
-- 특정 등급의 회원을 조회

-- 사용자 2의 트랜잭션
BEGIN TRANSACTION;
INSERT INTO User (user_id, membership) VALUES ('123', 'Gold');
-- 동일한 등급에 새로운 회원을 추가

COMMIT; -- 사용자 2의 트랜잭션을 커밋

-- 사용자 1의 트랜잭션에서 다시 조회
SELECT * FROM User WHERE membership = 'Gold';
-- 새로운 회원이 포함된 처음과 다른 결과를 반환 (Phantom Read, Non-Repeatable Read)

사용자 1membership 컬럼이 Gold인 회원을 조회한다. 그 후 사용자 2가 동일 등급에 user_id123인 회원을 새로이 INSERT한 후 커밋한다. 다시 사용자 1membership 컬럼이 Gold인 회원을 조회할 때에, 이전에 사용자 2가 추가한 데이터가 포함된 결과를 반환되어, 동일 쿼리에 대한 결과가 다름을 알 수 있다. (Phantom Read, Non-Repeatable Read)

3. REPEATABLE READ (반복 가능한 읽기)

Non-Repeatable Read 문제를 해결한 격리 수준으로, 동일한 쿼리를 여러 번 실행하더라도 동일한 결과를 반환한다. Undo 로그를 통해 자신보다 낮은 트랜잭션 번호가 갖는 커밋 데이터를 읽을 수 있다. 따라서 Non-Repeatable Read를 방지하지만, Phantom Read가 발생할 수 있다.

관련한 예시를 들어보겠다.

-- 사용자 1의 트랜잭션
BEGIN TRANSACTION;
SELECT COUNT(*) FROM User WHERE age > 19;
-- 성인 회원을 조회, 결과값 20

-- 사용자 2의 트랜잭션
BEGIN TRANSACTION;
INSERT INTO User (user_id, age) VALUES ('456', 22);
-- 새로운 회원 추가

COMMIT; -- 사용자 2의 트랜잭션을 커밋

-- 사용자 1의 트랜잭션에서 다시 조회
SELECT COUNT(*) FROM User WHERE age > 19;
-- 성인 회원을 조회, 결과값 20

사용자 1age 컬럼이 19 이상인 회원을 조회하고 20의 결과값을 얻었다. 그 후 사용자 2가 새로운 회원의 데이터를 INSERT한 후 COMMIT 한다. 다시 사용자 1age 컬럼이 19 이상인 회원을 조회할 때, 첫 번째 결과와 같이 20의 결과값이 나옴을 알 수 있다. 자신보다 낮은 트랜잭션 번호가 갖는 커밋 데이터만 읽을 수 있기에, 첫 번째 결과와 동일한 값이 도출되었다. 이를 통해 Non-Repeatable Read를 해결하고, 일관된 데이터를 보장할 수 있다.

Undo
트랜잭션이 진행되는 동안 변경된 데이터의 이전 상태를 저장하는 공간.

언두는 데이터베이스에서 트랜잭션을 롤백할 때 사용되는 로그 기록이다. 언두 영역을 통해 해당 트랜잭션이 시작될 때에 데이터를 유지한다. 이 때, 언두 영역에서는 각 변경 사항을 발생시킨 트랜잭션의 정보(트랜잭션 번호)도 함께 저장된다.

그럼, READ COMMITTED와 REPEATABLE READ는 무슨 차이일까?
READ COMMITTED는 트랜잭션이 실행중일 때 변경된 데이터의 가장 최신 데이터를 불러온다.
REPEATABLE READ 수준에서는 트랜잭션이 실행중일 때 변경된 데이터 중 해당 트랜잭션보다 이전에 발생한 변경사항만 보여주게 되어 NON-REPEATABLE READ 문제를 해결하게 된다.

4. SERIALIZABLE (직렬화)

가장 높은 격리 수준으로, 동시 처리 성능도 다른 트랜잭션 격리 수준보다 현저히 떨어진다. 모든 트랜잭션을 순차적으로 실행하는 것처럼 처리되고, 한 트랜잭션에서 읽고 쓰는 레코드를 다른 트랜잭션에서는 절대 접근할 수 없다. 또한, 실제로 데이터베이스 시스템이 각 트랜잭션을 적절하게 조율하여 동시성과 일관성을 유지하기 때문에, 트랜잭션 간의 충돌을 방지한다. Dirty Read, Non-Repeatable Read, Phantom Read를 방지할 수 있다.

profile
하이

0개의 댓글