1. Transaction
트랜잭션이란 질의(query)를 하나의 묶음 처리해서 만약 중간에 실행이 중단됐을 경우,
처음부터 다시 실행하는 Rollback 을 수행하고, 오류없이 실행을 마치면 commit 을 하는 실행 단위를 의미한다.
즉, 한 번 질의가 실행되면 질의가 모두 수행되거나 모두 수행되지 않도록 하는 매커니즘이다.
예를 들어, 친구에게 10,000원을 송금하는 상황을 가정해보자.
친구에게 송금을 한다면, 나의 계좌에서 10,000원이 줄고 친구의 계좌에 10,000원이 증가 되어야 하는데,
알 수 없는 오류로 인해 나의 계좌에선 10,000원이 줄었지만 친구 계좌에는 10,000원이 증가되지 않는다면 어떻게 될까?
매우 곤란한 상황일 것이다.
이러한 경우가 생기지 않도록 중간에 오류가 발생하면 다시 처음부터 송금을 하도록 하는 것이 rollback이다.
오류 없이 정상적으로 송금이 됐다면 정상적으로 실행이 끝났으므로 commit을 한다.
즉, 송금 과정이 하나의 트랜잭션이다.
참고로 트랜잭션을 작업수행의 논리적 단위라고 했는데, DBMS의 성능은 TPS : ( Transaction per second )로 측정한다.
트랜잭션의 사용 이유는 다음과 같다.
트랜잭션은 하나의 DB 서버에 여러 개의 클라이언트가 동시에 액세스 하거나 응용프로그램이 갱신을 처리하는 과정에서 중단될 수 있는 경우 등 데이터 부정합을 방지하고자 할 때 사용한다.
부정합이 발생하지 않으려면 프로세스를 병렬로 처리하지 않도록 하여 한 번에 하나의 프로세스만 처리하도록 하면 되는데, 이는 효율이 너무 낮다.
즉, 병렬로 처리할 수 밖에 없는 현실적인 상황으로 인해 부정합을 방지하고자 트랜잭션을 사용하는 것이다.
트랜잭션에서 중요한 것은 스케줄 관리입니다.
스케줄을 잘못 짜게 되면, 데드락에 빠지게 되는데 이와 관련된 주제가 뒤에서 살펴볼 2PL이다.
2. 특성
트랜잭션에는 아래와 같이 4가지의 특성이 있다.
4가지 특성의 앞 글자만 따서 ACID 특성 이라 한다.
원자성 ( Atomicity )
하나의 트랜잭션이 부분적으로 실행되거나 중단되지 않는 것을 보장하는 것을 말한다.
즉, All or Nothing의 개념으로서 작업 단위를 일부분만 실행하지 않는다는 것을 의미한다.
일관성 ( Consistency )
트랜잭션이 성공적으로 완료되면 일관적인 DB상태를 유지하는 것을 말한다.
여기서 말하는 일관성이란, 위의 송금 예제에서 금액의 데이터 타입이 정수형(integer)인데, 갑자기 문자열(string)이 되지 않는 것을 말한다.
격리성 ( Isolation )
트랜잭션 수행시 다른 트랜잭션의 작업이 끼어들지 못하도록 보장하는 것을 말한다.
즉, 트랜잭션끼리는 서로를 간섭할 수 없다.
지속성 ( Durability )
성공적으로 수행된 트랜잭션은 영원히 해당 상태가 반영이 되는 것을 말한다.
성공적인 트랜잭션 후에 commit을 하면 현재 상태는 영원히 보장된다.
이제부터 트랜잭션이 각 특성들을 어떻게 보장하고 있는지 알아보도록 하자.
3. 원자성 보장
트랜잭션에서 원자성은 수행하고 있는 트랜잭션에 의해 변경된 내역을 유지하면서, 이전에 commit된 상태를 임시 영역에 따로 저장한다 .
즉, 현재 수행하고 있는 트랜잭션에서 오류가 발생하면 현재 내역을 날려버리고 임시 영역에 저장했던 상태로 rollback 하는 것이다.
가장 최신의 데이터들이 임시로 저장되는 영역을 롤백 세그먼트(rollback segment) 라고 하며,
현재 수행하고 있는 트랜잭션에 의해 새롭게 변경되는 내역을 테이터베이스 테이블 이라고 한다.
다시 말하면, 트랜잭션의 원자성은 롤백 세그먼트에 의해 보장된다고 할 수 있다.
그런데 오류가 발생하면 rollback을 하는데, 트랜잭션의 길이가 길어지면 어떻게 될까?
확실하게 오류가 발생하지 않는 부분도 다시 처음부터 작업을 수행해야 한다.
따라서 확실한 부분에 대해서는 rollback이 되지 않도록 중간 저장 지점인 save point 를 지정할 수 있다.
save point를 지정하면 rollback 할 때 save point 이전은 확실하다 간주하고 그 이후부터 진행하게 된다.
4. 일관성 보장
트랜잭션에서 일관성은 트랜잭션 수행 전, 후에 데이터 모델의 모든 제약 조건(기본키, 외래키, 도메인, 도메인 제약조건 등)을 만족하는 것을 통해 보장 한다.
예를 들어, Movie와 Video 테이블이 있을 때 Video 테이블에 Movie 테이블의 primary key인 movie_id 가 외래키로 존재한다고 가정하자.
만약 movie_id 의 제약조건이 Movie 테이블에서 변경되면, Video 테이블에서도 movie_id 가 변경되어야 한다.
한 쪽의 테이블에만 데이터 변경사항이 이루어지면 안되는 것이죠.
그렇다면 어떻게 트랜잭션은 일관성을 보장할까?
어떤 이벤트 와 조건 이 발생했을 때, 트리거( Trigger ) 를 통해 보장한다.
트리거는 "방아쇠"인데, 트리거에 따라 데이터베이스 시스템이 자동적으로 수행할 동작 을 명시하는데 사용된다.
5. 고립성 보장
트랜잭션이 고립성을 보장하는 방법에 대해 이해하기 위해서는 병행 트랜잭션에 대해 먼저 알아야 합니다.
1) 병행 처리 ( concurrent processing )
CPU가 여러 프로세스를 처리하는 것처럼, 트랜잭션에 정해진 시간을 할당해서 작업을 하다가 부여된 시간이 끝나면 다른 트랜잭션을 실행하는 이런 방식으로 트랜잭션들을 조금씩 처리하는 것을 말한다.
그런데 이렇게 되면 많은 트랜잭션들이 조금씩 처리되는 과정에서 공통된 데이터를 조작할 수도 있는데, 이 경우 데이터가 혼란스러워 질 수 있다.
예를 들어, A 트랜잭션에서 x라는 데이터를 100으로 설정한 후 시간이 만료되어 B 트랜잭션으로 넘어갔다고 가정해보자.
B 트랜잭션에서는 x 데이터에 -50 연산을 해서 저장을 했을때, 시간이 만료되어 다시 A 트랜잭션이 실행될 경우 x 데이터의 값은 50이 될 것이다.
이렇게 트랜잭션이 조금씩 수행될 때, 공통된 데이터가 다른 트랜잭션에 의해 방해되면 안된다.
당연한 논리인게 내가 아닌 제 3자로 인해 의도된대로 데이터의 수정이 이루어지지 않았기 때문이다.
이와 같이 트랜잭션의 간섭이 일어날 경우 갱신분실, 오손판독, 반복불가능, 팬텀문제 등 여러 문제점들이 발생할 수 있다.
2) 고립성 보장
병행처리 과정에서 트랜잭션의 고립성이 왜 보장되어야 하는지를 알게되었다.
그러면 고립성을 어떻게 보장할 수 있을까요?
OS의 세마포어(semaphore)와 비슷한 개념으로 lock & excute unlock 을 통해 고립성을 보장할 수 있다.
즉, 데이터를 읽거나 쓸 때는 문을 잠궈서 다른 트랜잭션이 접근하지 못하도록 고립성을 보장하고, 수행을 마치면 unlock을 통해 데이터를 다른 트랜잭션이 접근할 수 있도록 허용하는 방식이다.
트랜잭션에서는 데이터를 읽을 때, 여러 트랜잭션이 읽을 수는 있도록 허용하는 shared_lock 을 한다.
즉, shared_lock은 데이터 쓰기를 허용하지 않고 오직 읽기만 허용한다.
또한 데이터를 쓸 때는 다른 트랜잭션이 읽을 수도 쓸 수도 없도록 하는 exclusive_lock 을 사용한다.
그리고 읽기, 쓰기 작업이 끝나면 unlock 을 통해 다른 트랜잭션이 lock을 할 수 있도록 데이터에 대한 잠금(lock)을 풀어준다.
그런데 lock과 unlock을 잘못 사용하면 데드락(deadlock)상태에 빠질 수 있다.
모든 트랜잭션이 아무것도 수행할 수 없는 상태가 되는 것이죠.
deadlock
3) 2PL 프로토콜 ( 2 Phase Locking protocol )
당연히 데드락이 걸리면 안되므로, 어떤 규칙에 의해서 고립성을 보장해야 한다는 2PL 프로토콜이 연구되었다.
2PL 프로토콜이란 여러 트랜잭션이 공유하고 있는 데이터에 동시에 접근할 수 없도록 하기위한 목적을 가진 프로토콜 이다.
이름 그대로 2가지 단계의 locking이 존재하는데, 한 가지는 growing phase 이고 다른 한 가지는 shrinking phase 입니다.
상승 단계란 read_lock , write_lock을 의미하고, 하강 단계란 unlock를 의미한다.
2PL 프로토콜은 상승 단계와 하강 단계와 섞이면 안된다는 것을 의미한다.
즉, lock과 unlock이 번갈아 수행되지 않고 lock이 쭉 수행된 후에 unlock이 쭉 수행되어야 한다는 것이 이 프로토콜이다.
2PL
정리하면 성능을 위해 병행처리를 해야하는데, 트랜잭션의 고립성을 보장하기 위해서는 2PL을 사용해야 한다는 것이다.
참고로 locking을 하는 방법에도 두 가지가 있다.
보수적 locking ( conservative locking )
트랜잭션이 시작되면 모든 lock을 얻는 방식으로서, 데드락이 발생하지 않지만 병행성이 좋지 못함
엄격한 locking ( strict locking )
트랜잭션이 commit을 만날 때까지 lock을 갖고 있다가 commit을 만날때 unlock을 하는 방식으로 데드락이 발생하지만 병행성이 좋음
일반적으로 병행성이 좋은 strict 방식을 사용.
더 자세한 내용은 여기 를 참고하자!
*참고 : victorydntmd.tistory.com/129