트랜잭션 (Transaction) 반드시 한번에 이뤄지거나 이뤄지지 않아야하는 작업의 단위를 말한다.
따라서, 트랜잭션이 발생 후 성공하면 데이터베이스에 완벽히 접근 혹은 데이터의 수정이 일어나야 하며 실패한다면 트랜잭션이 발생하기 이전 상태로 돌아가 데이터베이스 내 데이터들의 완전성을 보장해준다.
트랜잭션의 연산에는 2가지가 있다.
트랜잭션이 데이터베이스 내 데이터들의 완전성을 보장하기 위해서 가져야하는 4가지 특징들이 있다. 이 4가지 특징들의 알파벳 앞자리를 가져와 흔히 “ACID” 라고 말한다.
트랜잭션이 수행하던 도중 비정상적으로 종료되게 되면 종료되기 이전 정상적으로 수행되었던 작업이 데이터베이스에 반영된다면 데이터베이스를 신뢰할 수 없을 것이다.
따라서, 트랜잭션을 통해 변경되는 데이터베이스의 신뢰성을 보장하기 위해 모든 트랜잭션 작업들이 정상적으로 수행된 후 반영되던지 모든 작업이 수행되지 않고 이전 데이터베이스 상태를 유지해야 한다.
트랜잭션 작업이 성공적으로 완료된 이후에도 해당 트랜잭션 작업이 일어나기 전과 동일하게 데이터베이스 내 데이터들을 일관성있게 보장해야한다.
일련의 다른 트랜잭션 작업들이 동시에 수행되는 경우 각각의 트랜잭션 연산에 간섭없이 독립적으로 수행되어야 한다.
트랜잭션이 정상적으로 완료된 이후에는 데이터베이스 내에 작업이 영구적으로 반영되어 있어야 한다.
아직 커밋되지 않은 트랜잭션에서 처리중인 데이터를 다른 트랜잭션에서 읽는 것을 허용하는 격리 레벨로 데이터 정합성에서 문제가 많이 발생한다.
Dirty Read, Non-Repeatable Read, Phantom Read 이상현상이 모두 일어난다.
하나의 트랜잭션이 다른 트랜잭션에서 커밋 이전에 변경한 데이터 대해서 조회를 해서 사용하지만, 데이터 변경을 수행한 트랜잭션에서 에러가 롤백을 하게 된 경우 최종적인 DB 형태는 업데이트 이전 데이터를 가지고 있을 것이다.
이때, 커밋 이전 변경한 데이터는 최종 DB에 적재된 데이터와는 다른 값을 갖는 문제가 발생하는데 이러한 이상현상을 “Dirty Read”라고 한다.
# DB MEMBER -> 1(ID), '맹'(NAME), 20(AGE)
BEGIN TRANSACTION A
SELECT AGE FROM MEMBER WHERE NAME = '맹'; # AGE = 20 -> Tran A
UPDATE MEMBER SET AGE = 30 WHERE NAME = '맹'; # AGE = 30 -> Tran A
BEGIN TRANSACTION B
SELECT AGE FROM MEMBER WHERE NAME = '맹'; # AGE = 30 -> Tran B
# 서버 에러로 COMMIT 되지 않고 Transaction A Rollback
END
# DB MEMBER -> 1(ID), '맹'(NAME), 20(AGE)
A 트랜잭션에서는 해당 데이터를 Age = 30으로 업데이트를 하고, 커밋되기 이전에 B 트랜잭션이 Age를 조회했다. 이때 B 트랜잭션은 “30”이라는 값을 얻는다.
하지만, A 트랜잭션 수행 도중 서버 에러가 나서 Rollback을 한 경우 최종적으로 DB에 적재된 Age 값은 20이다.
따라서, B 트랜잭션에서 조회한 “Age=30”값은 잘못된 데이터이다.
트랜잭션 처리중인 데이터에 대해서는 커밋 된 이후의 데이터만 조회할 수 있는 격리레벨로 대부분의 서비스에서 Default로 사용된다.
Non-Repeatable Read, Phantom Read 이상 현상이 발생한다.
같은 트랜잭션 내에서 동일한 조회 쿼리를 수행한 경우에 조회하는 데이터가 다른 결과값을 갖는 이상현상을 “Non-Repeatable Read”라고 한다.
# DB MEMBER -> 1(ID), '맹'(NAME), 20(AGE)
BEGIN TRANSACTION A
SELECT AGE FROM MEMBER WHERE NAME = '맹'; # AGE = 20 -> Tran A
UPDATE MEMBER SET AGE = 30 WHERE NAME = '맹'; # AGE = 30 -> Tran A
BEGIN TRANSACTION B
SELECT AGE FROM MEMBER WHERE NAME = '맹'; # AGE = 20 -> Tran B
COMMIT;
END
SELECT AGE FROM MEMBER WHERE NAME = '맹' # AGE = 30 -> TRAN B
END;
# DB MEMBER -> 1(ID), '맹'(NAME), 30(AGE)
A 트랜잭션에서 Age 를 30으로 업데이트 하였다. B 트랜잭션은 격리레벨이 Read-Commited 단계이기 때문에 커밋하기 이전의 데이터인 “20”값을 조회한다.
이 후 , A 트랜잭션이 커밋을 완료한 후, B 트랜잭션이 동일한 쿼리로 Age를 다시 조회한 경우 조회되는 값은 “30”이다.
이 때, 같은 트랜잭션 내에서 동일한 쿼리로 데이터를 조회했을 때, Age값이 20 ≠ 30 인 경우가 발생한다.
트랜잭션 처리 이전에 커밋된 데이터에 대해서만 조회할 수 있는 격리레벨이다. 트랜잭션 내에서 읽어지는 동일 쿼리에 대해서는 항상 같은 데이터임을 보장한다.
Phantom Read 이상 현상이 일어난다.
같은 트랜잭션 내에서 동일한 조회 쿼리를 수행한 경우에 이전 조회에는 없던 데이터가 이후 쿼리 조회에서는 나타나거나 이전 조회에 있던 데이터가 이후 쿼리 조회에서는 없어지는 이상현상을 “Phantom Read”라고 한다.
# DB MEMBER -> 1(ID), '맹'(NAME), 20(AGE)
BEGIN TRANSACTION A
SELECT * FROM MEMBER; # 1(ID), '맹'(NAME), 20(AGE) -> Tran A
BEGIN TRANSACTION B
INSERT INTO MEMBER VALUES (2, 'Zayson', 28);
COMMIT;
END
SELECT * FROM MEMBER; # 1(ID), '맹'(NAME), 20(AGE) -> Tran A
END # 2**(ID), 'Zayson'(NAME), 28(AGE) -> Tran A**
# DB MEMBER -> 1(ID), '맹'(NAME), 20(AGE)
# -> 2(ID), 'Zayson'(NAME), 28(AGE)
A 트랜잭션에서 Member 테이블을 전체 조회하는 쿼리를 처음 수행했을 때는 하나의 행만 조회되었다. 이후 동일한 쿼리를 수행하기 전에 B 트랜잭션에서 Member 테이블에 데이터를 입력하고 커밋했다.
이 경우 A 트랜잭션에서 동일한 Member 테이블을 전체 조회하는 쿼리를 수행하면, 처음 조회 시에 나온 결과인 한개의 행이 아닌 두개의 행에 대한 데이터가 조회된다.
트랜잭션에서 사용중인 데이터를 다른 트랜잭션에서 접근할 수 없도록 하는 격리 레벨이다.
선행 트랙잭션이 데이터를 사용하고 있다면 해당 데이터에 Shared Lock을 걸어 다른 트랜잭션에서 Insert, Update, Delete 할 수 없도록 한다.
Dirty Read | Non-Repeatable Read | Phantom Read | |
---|---|---|---|
Read Uncommited | O | O | O |
Read Commited | O | O | X |
Repeatable Read | O | O | X |
Serializable | X | X | X |
Interview_Question_For_Beginner (JBee님 깃헙 기술 인터뷰 정리) : https://github.com/JaeYeopHan/Interview_Question_for_Beginner/tree/master/Database
[DB 기초] 트랜잭션이란 무엇인가? : https://coding-factory.tistory.com/226
[데이터베이스] 트랜잭션 격리수준] : https://velog.io/@guswns3371/데이터베이스-트랜잭션-격리수준
[10분 테코톡] 🌼 예지니어스의 트랜잭션 : https://www.youtube.com/watch?v=e9PC0sroCzc