- 데이터베이스의 상태를 변화시키는 하나의 논리적인 작업 단위
- DB에서 데이터를 다룰 때 장애가 일어난 경우 데이터를 복구하는 작업의 단위
- 트랜잭션은 전체가 수행되거나 또는 전체가 수행되지 않아야 한다. (ALL or Nothing)
- ex) 데이터베이스에 삽입, 수정, 삭제 등의 작업을 할 때, 여러 개의 작업들을 하나의 트랜잭션으로 묶습니다.
- Commit : 트랜잭션이 성공하여 트랜잭션 결과를 영구적으로 반영하는 연산
- Rollback : 트랜잭션 도중 실패하여 트랜잭션 실행을 취소하여 원래의 상태로 원상 복구시키는 연산
- 자동 커밋 : 자동 커밋으로 설정하면 각 쿼리는 실행 직후 자동으로 commit이 호출되어 결과가 반영된다.
- 따라서 commit이나 rollback을 따로 호출하지 않아도 되는 편리함이 있지만,
- 쿼리가 실행 직후 결과가 반영되기 때문에 트랜잭션 기능은 제대로 사용할 수 없다.
- set autocommit true;
- 수동 커밋 : 수동 커밋으로 설정하면 쿼리 실행 이후 꼭 commit이나 rollback을 호출해주어야 한다.
- 따라서 수동 커밋을 설정하는 것은 트랜잭션을 시작한다고 표현한다.
- set autocommit false;
- 트랜잭션과 Lock은 비슷한 목적과 비슷한 기능을 수행하는 것 같지만,
- Lock
- 동시성을 제어하기 위한 기능
- 여러 커넥션에서 동시에 동일한 자원을 요청할 경우 순서대로 한 시점에는 하나의 커넥션만 변경할 수 있게 해주는 역할
- 트랜잭션
- 데이터의 정합성을 보장하기 위한 기능
- 꼭 여러 개의 변경 작업을 수행하는 쿼리가 조합되었을 때만 의미 있는 개념은 아니다.
- 하나의 논리적인 작업 셋 중 하나의 쿼리가 있든 두 개 이상의 쿼리가 있든 관계없이 논리적인 작업 셋 자체가 100% 적용되거나 아무것도 적용되지 않아야 함을 보장하는 것
- Atomicity (원자성)
- 트랜잭션이 원자처럼 더 이상 쪼개지지 않는 하나의 프로그램 단위로 동작해야 한다는 의미
- 트랜잭션에 포함된 작업은 전부 수행되거나 전부 수행되지 않아야 한다.
- Consistency (일관성)
- 트랜잭션을 수행하기 전이나 후나 데이터베이스는 항상 일관된 상태를 유지해야 한다.
- ex) 어떤 테이블의 기본키와 같은 속성은 유지되어야 한다는 것 또는 A에서 B로 돈 이체를 할 때 A와 B계좌의 돈의 총합은 같아야 한다는 것 등이 있습니다.
- Isolation (고립성)
- 동시에 실행되는 둘 이상의 트랜잭션이 서로에게 영향을 미치지 않도록 격리한다.
- 수행 중인 트랜잭션이 완료될 때까지 다른 트랜잭션의 연산이 끼어들 수 없다.
- Isolation Level 중요
- Durability (영구성)
- 수행을 성공적으로 완료한 트랜잭션은 변경한 데이터를 영구히 저장해야 한다.
- 트랜잭션을 성공적으로 완료하면 그 결과는 항상 기록되어, 중간에 시스템에 문제가 발생하더라도 데이터베이스 로그 등을 사용해서 성공한 트랜잭션 내용을 복구해야 한다.
1) Read Uncommitted
- 트랜잭션에서 처리한 작업이 완료되지 않았음에도 불구하고 다른 트랜잭션에서 볼 수 있다.
- 커밋하지 않는 내용을 다른 트랜잭션이 접근가능
- Dirty Read 발생
- 데이터가 나타났다가 사라졌다 하는 현상
2) Read Committed
- 커밋이 완료된 트랜잭션의 변경사항만 다른 트랜잭션에서 조회 가능하다.
- 어떤 트랜잭션에서 변경한 내용이 커밋되기 전까지는 다른 트랜잭션에서 그러한 변경 내역을 조회할 수 없다.
- 오라클 DBMS의 디폴트 격리 수준
- Dirty Read 해결
- 다른 트랜잭션이 수정한 필드를 가져오는 경우 UNDO 영역에서 백업된 레코드를 가져온다.
- Non-Repeatable Read 발생
- 하나의 트랜잭션 내에서 동일한 SELECT 쿼리를 실행하였을 대 항상 같은 결과를 보장해야 하는 Repeatable read 정합성에 어긋난다.
3) Repeatable Read
- 트랜잭션 내에서 한번 조회한 데이터를 반복해서 조회해도 결과는 동일하다.
- Mysql의 InnoDB 스토리지 엔진에서 기본적으로 사용되는 격리 수준
- Non-Repeatable Read 해결
- 모든 InnoDB의 트랜잭션은 고유한 트랜잭션 번호(순차적으로 증가하는 값)를 가지며, 언두 영역에 백업된 모든 레코드에는 변경을 발생시킨 트랜잭션의 번호가 포함되어 있다.
- 자신보다 트랜잭션 ID가 작은 트랜잭션 번호에서 변경한 것만 보게 한다.
- Phantom Read 발생
- 다른 트랜잭션에서 수행한 변경 작업에 의해 레코드가 보였다가 안보였다가 하는 현상
- SELECT .. FOR UPDATE 쿼리는 SELECT 하는 레코드에 쓰기 잠금을 걸어야 하는데, 언두 레코드에는 잠금을 걸 수 없다.
- 따라서 위와 같은 쿼리는 언두 영역의 변경 전 데이터를 가져오는 것이 아니라 현재 레코드의 값을 가져오게 된다.
- InnoDB에서는 독특한 특성 때문에 REPEATABLE READ 격리 수준에서도 PHANTOM READ가 발생하지 않는다.
- InnoDB의 다중 버전 동시성 제어(MVCC, Multi-Version Concurrency Control) 메커니즘
- MVCC는 각 트랜잭션에 대해 데이터의 이전 버전을 유지하고, 각 트랜잭션에게 일관된 스냅샷을 제공하는 방식
- 트랜잭션의 시작 시점에서 스냅샷이 생성된다. 이 스냅샷은 해당 트랜잭션이 읽은 데이터의 일관성을 유지하기 위해 사용된다. InnoDB는 새로운 데이터를 삽입하거나 기존 데이터를 변경할 때 해당 데이터의 이전 버전을 유지하면서 새로운 버전을 생성하며, 새로운 버전은 트랜잭션이 커밋될 때까지 다른 트랜잭션에게 보이지 않는다.
- REPEATABLE READ 격리 수준에서 InnoDB는 쿼리를 실행할 때 트랜잭션의 스냅샷을 사용하므로, 다른 트랜잭션에 의해 새로운 행이 삽입되어도 해당 트랜잭션이 그 행을 볼 수 없게 된다.
- InnoDB 스토리지 엔진은 레코드 락과 갭 락을 합친 넥스트 키 락을 사용
- InnoDB의 다중 버전 동시성 제어(MVCC, Multi-Version Concurrency Control) 메커니즘
4) Serializable
- 읽기 작업도 공유 잠금을 획득해야 하며, 동시에 다른 트랜잭션은 그러한 레코드를 변경할 수 없다.
- 트랜잭션에서 읽고 쓰는 레코드를 다른 트랜잭션에서는 절대 접근할 수 없다.
격리 수준이 높을수록 데이터 일관성은 강화되지만 동시성과 성능은 약화된다.
반대로 격리 수준이 낮을수록 동시성과 성능은 향상되지만 데이터 일관성에 대한 보장은 약화될 수 있다.
Isolation Level | Dirty Read | Non-Repeatable Read | Phantom Read |
---|---|---|---|
Read Uncommitted | O | O | O |
Read Committed | O | O | |
Repeatable Read | O | ||
Serializable |
- Active(활동) : 트랜잭션이 실행 중인 상태
- Failed(장애) : 트랜잭션이 실행에 오류가 발생하여 중단한 상태
- Aborted(철회) : 트랜잭션이 비정상적으로 종료되어 Rollback 수행하는 상태
- Partially committed(부분 완료) : 트랜잭션이 마지막 연산까지 실행했지만, Commit 연산이 실행되기 직전인 상태
- Committed(완료) : 트랜잭션이 성공적으로 종료되어 Commit 연산을 실행한 후의 상태
- Partially committed와 Committed의 차이점
- Partially committed은 Commit 요청이 들어왔을 때
- Commited은 Commit을 정상적으로 완료하였을 때
- 트랜잭션은 꼭 필요한 최소의 코드에만 적용하는 것이 좋다.
- 트랜잭션 범위를 최소화하는 것이 중요
- 일반적으로 데이터베이스 커넥션 개수는 제한이라, 커넥션을 소유하는 시간이 길어지면 사용 가능한 여유 커넥션의 개수도 줄어들게 된다.
- 커넥션이 부족하면 커넥션을 기다리는 요청이 많아진다.
- 따라서 교착상태가 발생하지 않도록 유의해야 한다.
- 교착 상태의 빈도를 낮추는 법
- 트랜잭션을 자주 커밋한다. → 트랜잭션 범위를 최소화
- 읽기 잠금 획득 (SELECT ~ FOR UPDATE)의 사용을 피한다.