Transaction
트랜잭션은 여러 작업들을 하나로 묶은 단위로, 이렇게 묶인 작업들은 모두 실행되거나 모두 실행되지 않는다(all-or-nothing). 읽기와 쓰기를 하나의 논리적 단위로 묶는 방법이라고 정의할 수 있다.
이러한 보장을 해주는 이유는 위의 다이어그램의 작업을 진행한다고 할 때, 데이터를 쓰고 나서 지우는 과정에서 실패하여 읽기부터 다시 작업을 진행한다면 쓰기 과정이 2번 반복 되면서 원치 않는 결과를 초래할 수 있다. 이를 방지하기 위해 도입한 것이 트랜잭션인데, 하나의 트랜잭션 안에서 실패하면 트랜잭션 내부에서 했던 작업이 다시 진행하여도 동일한 결과를 얻을 수 있도록 트랜잭션의 처음으로 rollback하여 내부에서 진행했던 작업을 초기화한다. 트랜잭션 내에서 실패하지 않고 정상적으로 동작을 하면 commit을 통해 조작한 데이터를 실제적으로 적용한다.
ACID 특성
ACID는 데이터의 유효성을 보장하기 위해, 트랜잭션의 특징들이다.
•
Atomicity(원자성)
Multi Thread와 같은 환경에서 다른 thread에서 일부만 완료된 작업에 대해서 손댈 수 없고, 완전히 완료된 작업만 데이터를 읽고 조작할 수 있도록 한다. 이렇게하여 실행 전과 후의 상태만 존재하는, 해당 작업을 더 이상 쪼개어 동작할 수 없다는 특성이다. 즉, 원자성을 가진 작업을 통해서는 해당 작업의 전부가 반영되거나 아무것도 변하지 않아야한다.
•
Consistency(일관성)
트랜잭션이 성공적으로 완료되면 데이터베이스가 ‘좋은 상태’를 유지해야 한다는 개념이다. 일관성의 의미는 데이터는 항상 제대로 저장된 올바른 상태여야하고, 데이터에 관한 불변식이 존재한다는 것이다. 예를 들자면 입력 전에는 문자열(varchar)인데, 입력 후에 정수형(integer)으로 바뀌는 일이 존재하지 않아야한다는 것이다. ‘Primary Key는 Unqiue해야한다’와 같은 DB에서 관리하는 책임도 존재하지만, 일반적으로 불변식은 application에서 관리할 책임이고 DB는 데이터를 저장할 뿐이기 때문에 application에서 데이터의 유효성을 검증해야한다.
•
Isolation(격리성)
DB를 사용할 때 여러 클라이언트가 요청하게 되는데, 동일한 데이터에 동시 접근하게 되면 동시성 문제가 발생한다. 격리성은 이러한 문제를 발생하지 않게 하기 위해, 동시에 실행되는 transaction은 서로 격리되어 하나의 트랜잭션이 다른 트랜잭션을 방해할 수 없다는 것이다.
각 트랜잭션이 유일한 트랜잭션인 것처럼 격리시켜, 각 트랜잭션이 순차적으로 실행되었을 때와 동일한 결과를 보여주기 때문에 격리성은 직렬성으로 표현되기도 한다.
•
Durability(지속성)
지속성은 성공적으로 commit된 데이터가 비휘발성 저장소에 저장되어 H/W 결함이나 DB가 내려가더라도 보존되는, 데이터가 손실될 염려 없이 안전한 저장소를 제공하는 것이다.
트랜잭션의 격리 수준
트랜잭션의 격리 수준에는 Read Uncommitted, Read Committed, Repeatable Read, Serializable이 있다.
Read Uncommitted
•
Read Uncommitted 수준에서는 어떤 트랜잭션의 Rollback이나 Commit과 상관없이 다른 트랜잭션에서 수정된 사항을 읽을 수 있다.
트랜잭션 A : 10번 사원의 나이를 28살로 변경
트랜잭션 B : 10번 사원의 나이를 조회 -> 28살 조회
트랜잭션 A : 트랜잭션 Rollback -> 10번 사원 27살
Plain Text
복사
•
이와 같은 dirty read 현상이 발생할 수 있고, 데이터 정합성 문제가 많기 때문에 사용되지 않는다. RDBMS에서는 표준 격리수준으로 인정하지도 않는다.
dirty read : commit 되지 않은 데이터가 조회됨
Read Committed
•
어떤 트랜잭션에서 수정을 한 후 commit까지 완료되어야 다른 트랜잭션에서 수정된 사항을 읽을 수 있다.
•
Oracle RDBMS에서의 기본(default) 격리 수준이고, 온라인에서 가장 많이 사용되는 격리 수준이다.
트랜잭션 A : 10번 사원의 나이를 조회 -> 27살 조회
트랜잭션 B : 10번 사원의 나이를 28살로 수정 및 commit
트랜잭션 A : 10번 사원의 나이를 조회 -> 28살 조회
Plain Text
복사
•
같은 트랜잭션에서는 반복해서 조회해도 동일한 데이터가 조회되어야 하지만, 이처럼 반복 조회했을 때 데이터 정합성이 깨지는 non-repeatable read 문제가 발생할 수 있다.
non-repeatable read : 여러 트랜잭션이 동시 발생했을 때, 이전 조회의 결과와 이후 조회의 결과가 다르게 보이는 현상
Repeatable Read
•
트랜잭션이 시작 되기 전에 커밋된 내용에 대해서만 읽을 수 있는 트랜잭션 격리 수준이다.
•
Repeatable Read에서는 변경 전의 상태를 Undo 로그에 복사하여 백업 해두는데, 동일한 레코드에 대해 여러 버전의 데이터가 존재하기 때문에 이를 MVCC(Multi-Version Concurrency Control, 다중 버전 동시성 제어)라고 부른다.
•
각각의 트랜잭션은 순차 증가하는 고유 번호를 가지는데, Undo 로그에 복사되는 트랜잭션 고유 번호를 저장해둔다. 이 Undo 로그를 통해 반복해서 읽더라도 트랜잭션 시작 시의 커밋된 데이터만 읽는 것을 보장한다.
•
트랜잭션이 완료되어 commit이 수행되면 Undo 로그의 데이터를 실제 테이블에 반영하여 수정한다.
•
MySQL RDBMS에서의 기본(default) 격리 수준이고, non-repeatable read 현상이 발생하지 않는다.
# 트랜잭션 A : 10번 사원의 이름을 조회 -> user1
select name from users where id = 10;
# 트랜잭션 B : 10번 사원의 이름을 tester1 수정 및 commit
update users set name = 'tester1' where id = 10;
# 트랜잭션 A : 10번 사원의 이름을 조회 -> user1
select name from users where id = 10;
# 트랜잭션 A : user1 이름을 가진 사원의 이름을 user2로 변경 -> 실패
update users set name = 'user2' where name = 'tester1'; # 실패
SQL
복사
•
이처럼 update 부정합이 발생할 수 있다.
# 트랜잭션 A : 10번 사원의 이름을 조회 -> user1
select name from users where id = 10;
# 트랜잭션 B : 10번 사원의 이름을 tester1 수정 및 commit
update users set name = 'tester1' where id = 10;
# 트랜잭션 A : 10번 사원의 이름을 락을 얻으며 조회 -> tester1 -> 부정합 발생
select name from users where id = 10 for update;
SQL
복사
•
또한 위처럼 일반적인 데이터 조회 시에는 Undo 로그에서 조회하지만, For Update와 같이 잠금을 획득하며 조회하는 경우에는 실제 테이블에서 조회하기 때문에 phantom read 현상이 발생할 수 있다. 이는 Undo 로그가 append-only 형태로 잠금을 수행할 수 없기 때문에 잠금을 얻기 위해서는 실제 테이블에서 수행되기 때문이다.
phantom read
원래 조회되지 않아야 하는 데이터가 update 부정합으로 인해 조회가 되는 현상, delete에 의해서는 발생하지 않는다.
Serializable
•
가장 단순하고 가장 엄격한 트랜잭션 격리 수준이다.
•
innoDB(MySQL)에서는 기본적으로 읽기(조회) 수행 시 아무런 락(lock)을 걸지 않는데, 격리 수준을 Serializable로 하면 읽기 작업에도 Next-key Lock을 공유 락(Shared lock, S-lock)으로 잠근다.
•
공유 락을 트랜잭션이 끝날 때까지 유지하여, 트랜잭션에서 데이터를 수정하려고 하면 배타적 락(Exclucive lock, X-lock)을 잠근다. 하나의 트랜잭션에서 공유 락을 잠그고 있다면, 다른 트랜잭션에서는 배타적 락을 획득하지 못해 데이터를 수정할 수 없다.
•
이와 같은 특성 때문에 다른 격리 수준에 비해 동시 처리 능력이 떨어지고 성능이 저하된다.
정리
일반적으로 격리수준이 올라갈수록 트랜잭션 간 고립 수준이 올라가고 여러 문제가 해결되는 대신 성능이 저하된다.