1. 트랜잭션이란
- 데이터베이스의 상태를 변환시키는 하나의 논리적인 작업 단위
2. 트랜잭션의 성질(ACID)
- 원자성(Atomicity)
- 트랜잭션의 작업이 부분적으로 실행되거나 중단 될 수 없다.
- 트랜잭션의 모든 연산들은 정상적으로 수행 완료되거나 아니면 전혀 어떠한 연산도 수행되지 않은 상태를 보장해야 한다. (All or Nothing)
- 일관성(Consistency)
- 트랜잭션 완료 후 일관적인 DB 상태를 유지한다.
- ex) 데이터 타입이 정수 → 문자열이 될 수 없음.
- 독립성(Isolation)
- 하나의 트랜잭션이 실행하는 도중에 변경한 데이터는 이 트랜잭션이 완료될 때까지 다른 트랜잭션이 참조하지 못한다.
- 즉, 트랜잭션 끼리는 서로 간섭할 수 없다.
- 지속성(Durability)
- 성공적으로 수행된 트랜잭션은 영원히 반영이 된다.
- commit을 하게 되면 현재 상태는 영원히 보장된다.
2-1. 원자성을 보장하는 방법
- 현재 수행중인 트랜잭션에 의해 변경된 내용을 유지하면서, 이전에 commit된 상태를 임시 영역에 따로 저장함으로써 보장한다.
- 즉, 현재 수행하는 트랜잭션에서 오류가 발생하게 되면, 임시 영역에 저장한 데이터로 rollback을 하게 된다.
2-2. 일관성 보장
- 트랜잭션 수행 전, 후 데이터의 모델의 제약 조건을 만족하는 것으로 보장한다.
- 제약조건 : 기본키, 외래키, 도메인, 도메인 제약조건 등...
2-3. 독립성 보장
- 병행 처리 :
- 트랜젹션에 정해진 시간을 할당하여 작업을 하고, 정해진 시간 종료 후 다른 트랙잭션을 실행하는 방식으로 트랜잭션을 처리한다.
- 이런 경우, 공통된 데이터를 조작하게 되며, 데이터가 혼란스러워 질 수 있음.
- OS의 Semaphore와 비슷한 개념으로 lock & excute unlock을 통해 고립성을 보장한다.
- 즉, 데이터를 읽거나 쓸 때는 다른 트랜잭션이 접근하지 못하도록 lock을 걸어 고립성을 보장하고, 수행이 종료 된 후 unlock을 통해 다른 트랜잭션이 접근 할 수 있도록 한다.
- 단, 이과정에서 deadlock이 발생 할 수 있음
- 2PL 프로토콜(2 Phase Lock Protocol)
-
2PL 프로토콜이란 여러 트랜잭션이 공유하고 있는 데이터에 동시에 접근할 수 없도록 하기위한 목적을 가진 프로토콜
-
growing phase(상승단계): read_lock , write_lock
-
shrinking phase(하강단계) : unlock
-
상승과 하강 단계는 섞일 수 없음 → lock과 unlock이 번갈아 수행되지 않고 lock이 수행 된 후 unlock이 수행되어야 한다.
;
- 즉, 트랜잭션 처리 성능을 위해 병행 처리를 사용해야함 -> 이 과정에서 독립성을 보장하기 위하여 2PL 프로토콜을 사용한다.(deadlock이 발생하지 않기 위해)
- 보수적 locking ( conservative locking )
- 트랜잭션이 시작되면 모든 lock을 얻는 방식으로서, 데드락이 발생하지 않지만 병행성이 좋지 못함
- 엄격한 locking ( strict locking )
- 트랜잭션이 commit을 만날 때까지 lock을 갖고 있다가 commit을 만날때 unlock을 하는 방식으로 데드락이 발생하지만 병행성이 좋음
- 일반적으로 병행성이 좋은 strict 방식을 사용
3. 트랜잭션 격리수준(Isolation Level)
3-1. 트랜잭션 격리 수준이란.
- 트랜잭션에서 일관성이 없는 데이터를 허용하도록 하는 수준
- 즉, 실행 중인 트랜잭션의 중간결과를 다른 트랜잭션이 접근 할 수 없다.
- 이러한 격리성은 강하게~약하게 처리할 수 있다.
- 격리수준의 종류
- READ UNCOMMITTED(dirty read)
- READ COMMITTED
- REPEATABLE READ
- SERIALIZABLE
3-2. MySQL, MariaDB의 Lock
- MySQL, MariaDB 모두
InnoDB
를 사용하고 있어 이를 기반으로 정리
- MySQL의 Lock 레벨로는 row(행) 단위의 Lock을 기본으로 사용하며 Lock의 종류로는 공유(Shared) Lock과 배타(Exclusive) Lock을 사용
Shared Lock(S)
- Row-Level Lock
- select를 위한 Read Lock
- Shared Lock이 걸려있는 동안 다른 트랜잭션이 해당 row에 대해 X lock 획득(exclusive write)은 불가능하지만 S lock 획득(shared read)은 가능
- 한 row에 대해 여러 트랜잭션이 동시에
S
Lock 획득 가능
Exclusive Lock(X)
- Row-level lock
- write lock
- exclusive lock이 걸려있으면 다른 트랜잭션이 해당 row에 대해 X, S lock을 모두 획득하지 못하고 대기해야 한다.
Record Lock(Row Lock)
Record Lock이란 row index를 기준으로 Lock을 사용합니다.
예를 들어 SELECT c1 FROM t WHERE c1 = 10 FOR UPDTAE;
행을 다른 트랜잭션이 변경 할 수 없도록 합니다.
Record Lock은 테이블에 인덱스가 정의 되지 않은 경우에도 innoDB에서 숨겨진 클러스터 인덱스를 생성하고 이를 Record Lock에 활용합니다.
Record Lock 이 설정된 것을 확인한 로그
mysql > SHOW ENGINE INNODB STATUS;
RECORD LOCKS space id 58 page no 3 n bits 72 index `PRIMARY` of table `test`.`t`
trx id 10078 lock_mode X locks rec but not gap
Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
0: len 4; hex 8000000a; asc ;;
1: len 6; hex 00000000274f; asc ;;
2: len 7; hex b60000019d0110; asc ;;
Gap Lock
GAP LOCK은 인덱스 레코드 간의 GAP, 첫번째 인덱스 레코드의 앞 혹은 마지막 인덱스 레코드 뒤의 GAP에 대한 LOCK 이다.
예를 들어, 모든 기존 값 사이의 GAP이 잠겨있기 때문에 열에 해당 값이 이미 있는지 여부에 관계 없이
SELECT c1 FROM t WHERE BETWEEN 10 and 20 FOR UPDATE;
와 같은 쿼리를 실행 하였을 때 다른 트랜잭션이 이 사이에 값을 삽입하지 못하도록 한다.
Next-Key Lock
Next-Key Lock은 Record Lock 과 GAP Lock이 조합된 형태의 Lock 이다.

3-3. 트랜잭션의 격리수준(Transaction Isolation)
- READ UNCOMMITTED
- 각 트랜잭션의 변경 내용이 COMMIT, ROLLBACK 여부에 상관없이 다른 트랜잭션에서 보여짐 → 신뢰할 수 없는 데이터
- 데이터 베이스의 일관성을 유지 불가
DIRTY READ
현상 발생
- 트랜잭션 작업이 완료되지 않았는데도 다른 트랜잭션에서 볼 수 있는 현상
- A 트랜잭션에서 10번 사원의 나이를 27 → 28 로 변경, commit 하지 않음
- B 트랜잭션에서 10번 사원의 나이를 조회 → 28로 조회됨 DIRTY READ 발생
- 이후 A 트랜잭션에서 RollBack을 실행
- B트랜 잭션은 28로 이미 결과를 받아서 로직을 수행
- 데이터 정합성 문제 발생
- READ COMMITTED
- 커밋이 완료된 트랜잭션의 변경사항만 다른 트랜잭션에서 조회할 수 있도록 허용하는 격리 수준
- 특정 트랜잭션에서 데이터가 변경이 되었으나, 커밋되지 않은 상태에서는 다른 트랜잭션에서는 변경전의 데이터를 읽어옴
- 변경전의 데이터는 실제 테이블에서 값을 가져오는 것이 아닌
Undo
영역에서 백업된 레코드 값을 가져옴
undo log
데이터를 변경했을 때 변경되기 전의 데이터를 보관하는 영역
트랜잭션의 롤백 대비용, 트랜잭션의 격리 수준을 유지하면서 높은 동시성을 제공하기 위해 사용
- Oracle의 기본적인 격리수준
- Non-Repeatalbe Read 발생
- B 트랜잭션에서 10번 사원의 나이를 조회 : 27로 조회됨
- A 트랜잭션에서 10번 사원의 나이를 27 → 28 로 변경 후 커밋
- B 트랜잭션에서 10번 사원의 나이를 조회 : 28로 조회됨 → 같은 트랜잭션에서 다른 값이 조회됨
- REPEATABLE READ
- 트랜잭션이 완료될 때까지 SELCT 문장이 사용되는 데이터에
S
Lock이 걸리는 계층
- 특정 행을 조회시 항상 같은 데이터를 응답하는 것을 보장
- 다른 사용자는 트랜잭션 영역에 해당되는 데이터에 대한 수정 불가능
- MySQL에서는 트랜잭션마다 ID를 부여하여 트랜잭션 ID 보다 작은 트랜잭션 번호에서 변경한 값만 읽음
Undo
공간에 백업해두고 실제 레코드 값을 변경
- 백업된 데이터는 불필요하다고 판단되는 시점에 주기적으로 삭제
Undo
에 백업된 데이터가 많아지면 성능이 저하 될 수 있음
- 10번 트랜잭션에서 500번 사원의 이름 조회
- 11번 트랜잭션이 500번 사원의 이름을 변경하고 커밋
- 10번 트랜잭션이 500번 사원의 이름을 다시 조회 :
undo
에 백업된 데이터 반환
- PHANTOM READ 발생
- 다른 트랙잭션에서 수행한 변경작업에 의해 레코드가 보였다가, 안보였다가 하는 현상
- 이를 방지하기 위해 쓰기 잠금이 필요
InnoDB
에서는 next-key locking
알고리즘을 사용하고 있어 Repeatable Read에서도 Phantom Read를 예방
- MySQL의 기본적인 격리수준
- SERIALIZABLE
- 가장 높은 수준의 격리 레벨
- 성능 측면에서는 동시 처리성능이 가장 낮음 → 잘 사용되지 않음
- InnoDB에서는
SELECT
문을 모두 SELECT... FOR SHARE
문으로 변환하여 S
LOCK을 통한 LOCKING READ를 수행하게 한다.
참고자료 및 이미지 출처