1. 트랜잭션이란?
트랜잭션(Transaction)은 업무 처리를 위한 데이터베이스의 논리적인 작업 단위입니다. 하나의 트랜잭션은 한 개 이상의 연산으로 이루어질 수 있으며, 연산들은 완전히 처리되거나 하나도 처리되지 않아야 합니다.
2. 트랜잭션 제어어(Transaction Control Language, TCL)
1) COMMIT : 하나의 트랜잭션을 정상적으로 완료하고 그 결과를 데이터베이스에 반영하는 명령어 입니다.
2) ROLLBACK : 트랜잭션을 취소하기 위한 명령어 입니다. 데이터베이스가 트랜잭션 도중에 비정상적으로 종료되면 자동으로 ROLLBACK이 됩니다.
3) SAVEPOINT : 하나의 트랜잭션을 여러 지점으로 분리하여 저장하는 명령어 입니다. SAVEPOINT 지점명으로 SAVEPOINT를 설정하고, ROLLBACK TO 지점명으로 해당 SAVEPOINT까지 ROLLBACK을 할 수 있습니다.
3. 트랜잭션의 특성: ACID
트랜잭션은 원자성(Atomicity), 일관성(Consistency), 격리성(Isolation), 영속성(Durability) 4가지 특성을 가집니다.
원자성은 트랜잭션의 작업이 부분적으로 실행되거나 중단되지 않는 것을 보장하는 성질을 말합니다. 즉, 트랜잭션에서 정의된 연산들은 모두 성공적으로 실행되거나 전혀 실행되지 않아야 한다는 것(All or Nothing)을 의미합니다. 예를 들어 인터넷 뱅킹으로 송금을 한다면 나의 계좌에서 금액이 차감되고 상대방의 계좌에서는 금액이 증가합니다. 두 연산은 모두 수행되어야하며, 중간에 오류가 발생한다면 ROLLBACK되어 모든 연산이 수행되지 않은 상태로 돌아가게 됩니다.
일관성은 트랜잭션이 실행되기 전과 후의 데이터베이스 상태가 일관되어야 한다는 성질을 말합니다. 예를 들어 송금을 하기 위해서는 수신자와 발신자의 데이터가 모두 입력되어야 하는 제약사항을 가지는데, 수신자의 계좌번호를 입력하지 않는다면 데이터베이스의 일관성이 변하게 되므로 트랜잭션이 수행되지 않습니다.
격리성은 모든 트랜잭션은 다른 트랜잭션으로부터 독립된다는 성질을 말합니다. 예를 들어 내 계좌에 10,000원이 있고 두 사람에게 동시에 6,000원씩 이체한다면 한꺼번에 12,000원이 송금되는 것이 아니라 첫번재 사람에게 먼저 송금 트랜잭션이 수행되고 순차적으로 2번째 사람에게 송금 트랜잭션이 수행됩니다. 이때 계좌의 금액은 0원 이상이 되어야 한다는 일관성이 유지되어야 하므로 2번째 송금은 수행되지 않습니다.
영속성은 트랜잭션이 성공적으로 수행될 경우 그 결과는 영구적으로 데이터베이스에 저장되는 성질을 말합니다. 송금 트랜잭션이 성공적으로 수행되었다면 송금 시스템이 재부팅 되더라도 송금 결과는 변하지 않아야 합니다.
4. 트랜잭션의 격리성 수준
트랜잭션의 격리성을 보장하기 위해서는 동시에 처리되는 트랜잭션을 차례대로 실행해야 하지만, 이 경우 처리 성능이 나빠질 수 있습니다. 이러한 문제로 인하여 ANSI/ISO SQL 표준에서는 격리 수준을 4가지로 정의합니다.
1) 0단계: REDAD-UNCOMMITTED
0단계에서는 트랜잭션에서 변경된 내용이 COMMIT이나 ROLLBACK의 여부에 상관없이 다른 트랜잭션에서 값을 읽을 수 있습니다.
REDAD-UNCOMMITTED 트랜잭션1에서 DOCUMENT_IDX가 2인 레코드를 INSERT하고 아직 COMMIT이 이루어지지 않았지만 트랜잭션2에서 DOCUMENT_IDX가 2인 레코드를 조회했습니다. 이 경우 트랜잭션1에서 문제가 발생하여 ROLLBACK이 되거나 UPDATE를 통해 내용을 수정하더라도 트랜잭션2에서는 잘못된 데이터를 가지고 처리를 하게 됩니다.
이렇게 아직 COMMIT 되지 않은 데이터를 읽어 일관성이 보장되지 않는 현상을 Dirty Read라고 합니다. 데이터 정합성 문제가 많기때문에 RDBMS에서는 거의 사용하지 않습니다.
2) 1단계: REDAD-COMMITTED
1단계에서는 COMMIT이 완료된 데이터만 다른 트랜잭션에서 값을 읽을 수 있습니다.
REDAD-UNCOMMITTED
트랜잭션1에서 DOCUMENT_IDX가 2인 레코드를 UPDATE하면 수정되기 전 레코드는 UNDO 영역에 백업이 됩니다. 트랜잭션2는 트랜잭션1의 COMMIT이 수행되기 전까지 UNDO 영역에 백업된 레코드를 읽게 되므로 Dirty Read 현상이 발생하지 않습니다.
그러나 트랜잭션1이 COMMIT을 수행한 후 트랜잭션2에서 동일한 레코드를 조회하면 값이 변경되어 일관성이 보장되지 않게 되는데, 이러한 현상을 Non-Repeatable Read라고 합니다.
오라클에서는 READ-COMMITTED를 기본 격리수준으로 사용합니다.
3) 2단계: REPEATABLE-READ
2단계에서는 트랜잭션이 완료되기 전까지 Shared-Lock을 적용하여 다른 트랜잭션에서 해당 데이터를 수정할 수 없도록 합니다.
REPEATABLE-READ
트랜잭션1이 COMMIT을 수행하더라도 트랜잭션2는 COMMIT을 수행하기 전까지 UNDO 영역에 백업된 데이터를 읽으므로 Non-Repeatable Read 현상이 발생하지 않습니다.
그러나 트랜잭션2가 COMMIT을 수행한다면 트랜잭션 중에 없던 레코드가 추가되거나 없어지는 현상이 발생하는데, 이러한 현상을 Pantom Read라고 합니다.
MySQL이나 MariaDB의 InnoDB 스토리지 엔진에서는 REPEATAbLE-READ를 기본 격리수준으로 사용하며, 다중 버전 병행 제어(Multi Version Concurrency Control, MVCC)를 통해 해당 격리 수준에서도 Pantom Read 현상이 발생하지 않습니다.
4) 3단계: SERIALIZABLE-READ
3단계는 가장 엄격한 격리 수준으로, 읽기 작업에도 Shared-Lock을 적용하기 때문에 트랜잭션이 COMMIT 되기 전까지 다른 트랜잭션에서는 해당 데이터를 수정하지 못할 뿐만 아니라 읽을 수도 없습니다. 그렇기 때문에 Pantom Read 현상이 발생하지 않습니다.
그러나 트랜잭션의 동시제어가 불가능하여 성능이 좋지 않기때문에 RDBMS에서는 거의 사용되지 않습니다. MySQL의 경우 위에서 설명한 MCVCC를 통해 REPEATABLE-READ 격리 수준에서도 Pantom Read 현상이 발생하지 않기 때문에 사용할 필요가 없어보입니다. |