트랜잭션
데이터 베이스의 상태를 변화시키기 위해 수행하는 작업의 단위
데이터 베이스의 상태를 변화 시킨다는 것은 아래의 질의어(SQL)을 이용해 데이터베이스를 접근하는 것을 말한다
여기서 작업의 단위는 한 SQL이 아닌, 많은 SQL 명령문들을 사람이 정하는 기준에 따라 정하는 것을 의미한다
예를 들어 게시판은 올리기 버튼을 누를경우 INSERT문으로 사용자의 데이터가 게시판에 옮겨지고, 그 후 게시판을 구성할 데이터를 SELECT하여 게시판을 최신정보로 유지한다. 여기서 작업의 단위를 INSERT와 SELECT문을 합친 것으로 생각할 수 있다. 이러한 작업의 단위를 트랜잭션이라 한다.
개발자는 하나의 트랜잭션을 잘해야 데이터를 효율적으로 다룰 수 있다.
ACID
-
원자성(Atomicity)
트랜잭션이 데이터베이스에 모두 반영되던가, 전혀 반영되지 않아야한다.
트랜잭션 단위로 데이터가 처리되어야한다
-
일관성(Consistency)
트랜잭션의 작업처리가 항상 일관성 있어야한다
-
독립성(Isolation)
둘 이상의 트랜잭션이 동시에 실행되고 있을 경우 어떤 하나의 트랜잭션이라도, 다른 트랜젝션의 연산에 끼어들 수 없다
하나의 특정 트랜잭션이 완료될 때까지, 다른 트랜잭션이 특정 트랜잭션의 결과를 참조할 수 없다
-
지속성(Durability)
트랜잭션이 성공적으로 완료되었을 경우, 결과는 영구적으로 반영되어야한다
원자성과 지속성은 회복 관리자 프로그램(undo, redo) 을 이용하는데, 일부만 진행된 트랜잭션을 취소시켜 원자성을 유지할 뿐 아니라 값을 트랜잭션 이전의 상태로 복원시켜 지속성을 유지시켜준다.
일관성과 독립성은 동시성 제어 알고리즘을 활용한다.
Commit, Rollback
- commit 하나의 트랜잭션이 성공적으로 끝났고, 데이터베이스가 일관성있는 상태에 있을 때 하나의 트랜잭션을 끝났다라는 것을 알려주기 위해 사용 이 연산을 사용시, 수행했던 트랜잭션이 로그에 저장되며, 후에 rollback연산을 수행했었던 트랜잭션 단위로 하는 것을 도와줌
- rollback 하나의 트랜잭션 처리가 비정상적으로 종료되어 트랜잭션의 원자성이 깨진 경우, 즉 트랜잭션이 정상적으로 종료되지 않았을 경우, 모든 쿼리문을 취소하고 이전 상태로 돌아가야한다
하지만, 실제로는 ACID 원칙은 종종 지켜지지 않는다. 왜냐하면 ACID 원칙을 strict 하게 지키려면 동시성이 매우 떨어지기 때문이다.
그렇기 때문에 DB 엔진은 ACID 원칙을 희생하여 동시성을 얻을 수 있는 방법을 제공한다. 바로 transaction의 isolation level이다
Isolation level
- 트랜젝션의 격리수준(isolation level)은 동시에 여러 트랜젝션이 처리될 때, 특정 트랜젝션이 다른 트랜젝션에서 변경하거나 조회하는 데이터를 볼 수 있도록 허용할지 말지를 결정하는 것.
- 격리 수준은 아래의 4가지가 존재 READ UNCOMMITTED, READ COMMITTED, REPEATABLE READ, SERIALIZABLE 가 존재한다.
- Isolation 원칙을 덜 지키는 level을 사용할수록 문제가 발생할 가능성은 커지지만 동시에 더 높은 동시성을 얻을 수 있다.

READ UNCOMMITTED
- DIRTY READ 라고도 한다. 일반적인 데이터베이스에서 거의 사용하지 않는다.
- 한 트랜잭션의 변경된 내용을 커밋이나 롤백과 상관 없이 다른 트랜잭션에서 읽을 수 있는 격리 수준
- 모든 부정합 문제 발생
READ COMMITTED
- 오라클에서 주로 사용됨
- COMMIT이 완료된 데이터 만 조회 가능한 격리 수준
- 더티 리드 해결
- NON-REPEATABLE-READ 부정합 문제 발생
- 커밋되기 전의 데이터를 읽을 경우 undo 영역에서 데이터를 읽어온다.
undo 로그
- 트랜젝션의 롤백 대비용
- 트랜젝션의 격리수준을 유지하면서 높은 동시성을 제공해준다.
- 원자성을 보장해준다.
REPEATABLE READ
- MySQL 의 InnoDB 스토리지 엔진에서 기본적으로 사용되는 격리 수준
- 트랜잭션이 시작되기 전에 커밋된 내용에 관해서만 조회할 수 있는 격리 수준
- NON-REPEATABLE-READ 부정합 해결
- undo 영역에 백업된 이전 데이터를 통해 트랜젝션 내에서는 동일한 결과를 보여주도록 보장하여 NON-REPEATABLE-READ 부정합 문제 해결
- READ COMMITED 격리 수준도 undo 영역을 활용하지만, 활용방식이 다르다.
- undo 영역에 백업된 레코드의 여러 버전 가운데 몇 번째 버전을 보여주냐에 차이가 있다. 즉 실행중인 트랜젝션(자신의 트랜젝션) 보다 작은 트랜젝션에서 변경한 데이터만 보게 한다.
- PHANTOM READ 부정합 발생
- InnoDB에서는 레코드락과 갭락을 합친 넥스트 키락을 사용해 PHANTOM READ 해결
SERIALIZABLE
- 격리수준이 높음/ 동시 처리 성능 떨어짐
- 읽기작업도 잠금을 획득해야한다. 따라서 한 트랜젝션에서 읽고 쓰는 레코드를 다른 트랜젝션에서는 절대 접근할 수 없다.
- 모든 부정합 문제 해결
- 동시성이 중요한 데이터베이스에서는 거의 사용되지 않는다.
부정합 문제
-
DIRTY READ
- 다른 트랜젝션에서 처리한 작업이 완료되지 않았음에도 불구하고 다른 트랜젝션에서 볼 수 있게 되는 현상
- 데이터가 나타났다가 사라졌다하는 현상을 초래함으로 트랜젝션의 격리 수준으로 인정하지 않을 정도로 데이터의 정합성에 악영향을 끼침 → 강력히 피해야한다.
- 해결 : READ COMMITTED 격리 수준 이상을 사용한다.
-
NON-REPEATABLE-READ
- 하나의 트랜잭션에서 동일한 SELECT 쿼리를 실행 했을 때 항상 같은 결과를 보장해야한다는 REPEATABLE READ 정합성에 어긋나는 것
- 예시로 동일한 데이터를 여러번 읽고 변경하는 작업이 금전적인 문제일 경우 문제가 커진다. 따라서 조회 시 마다 다른 결과를 가져와 REPEATABLE READ 를 보장하지 못한다.
- 해결 : REPEATABLE READ 격리 수준 이상을 사용한다.
-
PHANTOM READ
-
SELECT ... FOR UPDATE
쿼리와 같은 쓰기 잠금을 거는 경우 다른 트랜잭션에서 수행한 변경 작업에 의해 레코드가 보였다가 안 보였다가 하는 현상
-
SELECT ... FOR UPDATE 쿼리는 SELECT 하는 레코드에 쓰기 잠금을 걸어야 하는데, undo 레코드에는 잠금을 걸 수 없다. 그래서 undo 영역의 변경 전 데이터를 가져오는 것이 아니라 현재의 데이터 값을 가져오게 된다. 따라서 PHANTOM READ 부정합 발생
SELECT FOR UPDATE 쿼리
- 가장 먼저 LOCK을 획득한 SESSION의 SELECT 된 ROW들이 UPDATE 쿼리후 COMMIT 되기 이전까지 다른 SESSION들은 해당 ROW들을 수정하지 못하도록 하는 기능
- 데이터를 수정하기위해 SELECT하는 중이기 때문에 다른 트랜잭션이 레코드를 SELECT하지 못하게 read, write락을 거는 구문
-
해결 : SERIALIZABLE 격리 수준을 사용하거나, REPEATABLE READ 격리 수준을 사용하는 InnoDB 스토리지 엔진을 이용
동시성 제어
둘 이상의 트랜잭션이 동시에 수행될 때, 일관성을 해치지 않도록 트랜잭션의 실행 순서를 제어하는 것
동시성과 일관성
동시성을 높이려고 잠금을 최소화하면 일관성이 깨지고, 일관성을 유지하려고 잠금의 사용을 늘리면 동시성이 떨어진다. 둘은 반비례 관계이다. 따라서 주의가 필요하다.
2개 이상의 트랜잭션이 하나의 값에 접근하는 경우, 1개는 쓰고 1개는 읽는 트랜잭션일 경우 상황에 따라 부정합 문제가 발생한다. 또한 2개의 트랜잭션이 모두 쓰기 시 무제어 병행 수행을 하는 경우 갱신손실(Lost Update) , 모순성(Inconsistency) , 연쇄 복귀(Cascading Rollback) 의 문제가 발생한다.
이러한 문제점을 해결하기 위해 동시성 제어 기법을 수행한다.
Lock
락킹 기법: 트랜잭션들이 동일한 데이터 항목에 대해 임의적인 병행 접근을 하지 못하도록 제어하는 것
공유락(LS, Shared Lock)
- 특정 트랜잭션이 데이터 항목 X 에 대해 공유락을 설정한 경우 트랜잭션 T는 해당 데이터 항목 X를 읽을 수는 있지만 기록은 불가하다.
- 다른 트랜잭션도 Shared-Lock 이 설정된 X 에 대해 Shared-Lock 설정 가능
배타락(LX, Exclusive Lock)
- 특정 트랜잭션이 데이터 항목 X 에 대해 배타락을 설정한 경우 트랜잭션 T는 해당 데이터 항목 X를 읽을 수도 기록할 수도 있다.
- 다른 트랜잭션은 Exclusive-Lock 이 설정된 X 에 대해 Exclusive-Lock 설정 불가능
공유락/ 배타락 규칙
- 데이터에 락이 걸려있지 않으면 트랜잭션은 데이터에 락을 걸 수 있다.
- 트랜잭션이 데이터 X를 읽기만 할 경우 LS(X)를 요청하고, 읽거나 쓸 경우 LX(X)를 요청한다.
- 트랜잭션이 락을 허용하지 못하면 대기 상태가 된다.
- LS(X) 를 걸어둔 경우 LS(X)는 허용하고 LX(X)는 허용하지 않는다.
- LX(X)를 걸어둔 경우 LS(X), LX(X) 모두 허용하지 않는다.
Two Phase Locking, 2단계 락킹
- 공유락과 배타락을 사용하다가 결과값이 달라지는 이상현상을 unlock과 LX/LS의 순서를 바꿈으로써 해결하는 기법이다.
- 락을 걸고 해제하는 시점에 제한을 두어 두 개의 트랜잭션이 동시에 실행될 때 데이터의 일관성을 유지
확장 단계, Growing Phase
- 트랜잭션이 데이터를 읽거나 쓸 때 잠금을 획득하는 단계
- 이 단계에서는 트랜잭션이 필요한 데이터를 접근하기 위해 배타잠금(Exclusive Lock)이나 공유잠금(Shared Lock)을 요청
- 잠금 요청이 승인되면 잠금을 획득하고, 잠금을 획득하기 전까지 다른 트랜잭션은 해당 데이터에 대한 잠금을 요청할 수 없다
축소 단계, Shrinking Phase
- 트랜잭션이 데이터를 사용하고 더 이상 잠금이 필요하지 않을 때 잠금을 해제하는 단계
종류
- Strict 2PL: 커밋이나 abort 가 일어나기 전까진 lock 해제 불가
- Conservative 2PL: 트랜잭션 시작 전 모든 item 에 대해 lock 을 갖고 시작하므로 데드락이 발생하지 않는다
데드락
두 개 이상의 트랜잭션이 각각 자신의 데이터에 대해 락을 획득하고 상대방 데이터에 대해 락을 요청해 무한 대기상태에 빠지는 현상, 교착현상