1. 트랜잭션( transaction )

트랜잭션이란 질의(query)를 하나의 묶음 처리해서 만약 중간에 실행이 중단됐을 경우,

처음부터 다시 실행하는 Rollback을 수행하고, 오류없이 실행을 마치면 commit을 하는 실행 단위를 의미합니다.

즉, 한 번 질의가 실행되면 질의가 모두 수행되거나 모두 수행되지 않는 작업수행의 논리적 단위입니다.


예를 들어, 친구에게 인터넷 뱅킹으로 10,000원을 송금하는 상황을 가정해보겠습니다.

제가 친구에게 송금을 한다면, 저의 계좌에서 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 Noting의 개념으로서 작업 단위를 일부분만 실행하지 않는다는 것을 의미합니다.
  • 일관성 ( 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 )를 통해 보장합니다.

트리거는 "방아쇠"인데, 데이터베이스 시스템이 자동적으로 수행할 동작을 명시하는데 사용됩니다.

어떤 행위의 시작을 알리는 것이죠.

위의 코드는 트리거가 호출되면, 수행할 질의들을 트리거로 생성해서 작성한 코드입니다.

create는 트리거를 생성하는 코드이고, after는 트리거가 실행되기 위한 event를 나타냅니다.





5. 고립성 보장

트랜잭션이 고립성을 보장하는 방법에 대해 이해하기 위해서는 병행 트랜잭션에 대해 먼저 알아야 합니다.


1) 병행 처리 ( concurrent processing )

CPU가 여러 프로세스를 처리하는 것처럼, 트랜잭션에 정해진 시간을 할당해서 작업을 하다가 부여된 시간이 끝나면 다른 트랜잭션을 실행하는 이런 방식으로 트랜잭션들을 조금씩 처리하는 것을 말합니다.

그런데 이렇게 되면 많은 트랜잭션들이 조금씩 처리되는 과정에서 공통된 데이터를 조작하게 되는데, 이 경우 데이터가 혼란스러워 질 수 있습니다.


예를 들어, A 트랜잭션에서 x라는 데이터를 100으로 설정한 후 시간이 만료되어 B 트랜잭션으로 넘어갔다고 가정해보겠습니다.

B 트랜잭션에서는 x 데이터에 -50 연산을 해서 저장을 했을때, 시간이 만료되어 다시 A 트랜잭션이 실행될 경우 x 데이터의 값은 50이 됩니다.

이렇게 트랜잭션이 조금씩 수행될 때, 공통된 데이터가 다른 트랜잭션에 의해 방해되면 안됩니다.

이와 같이 트랜잭션의 간섭이 일어날 경우 갱신분실, 오손판독, 반복불가능, 팬텀문제 등 여러 문제점들이 발생합니다.




2) 고립성 보장

병행처리 과정에서 트랜잭션의 고립성이 왜 보장되어야 하는지를 알게되었습니다.


그러면 고립성을 어떻게 보장할 수 있을까요?

OS의 세마포어(semaphore)와 비슷한 개념으로 lock & excute  unlock을 통해 고립성을 보장할 수 있습니다.

즉, 데이터를 읽거나 쓸 때는 문을 잠궈서 다른 트랜잭션이 접근하지 못하도록 고립성을 보장하고, 수행을 마치면 unlock을 통해 데이터를 다른 트랜잭션이 접근할 수 있도록 허용하는 방식입니다.


트랜잭션에서는 데이터를 읽을 때, 여러 트랜잭션이 읽을 수는 있도록 허용하는 shared_lock을 합니다.

즉, shared_lock은 데이터 쓰기를 허용하지 않고 오직 읽기만 허용합니다.


또한 데이터를 쓸 때는 다른 트랜잭션이 읽을 수도 쓸 수도 없도록 하는 exclusive_lock을 사용합니다.

그리고 읽기, 쓰기 작업이 끝나면 unlock을 통해 다른 트랜잭션이 lock을 할 수 있도록 데이터에 대한 잠금(lock)을 풀어줍니다.


그런데 lock과 unlock을 잘못 사용하면 데드락(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을 사용해야 한다는 것입니다. ( Serializable Schedule )


참고로 locking을 하는 방법에도 두 가지가 있습니다.
  • 보수적 locking ( conservative locking )
    • 트랜잭션이 시작되면 모든 lock을 얻는 방식으로서, 데드락이 발생하지 않지만 병행성이 좋지 못함
  • 엄격한 locking ( strict locking )
    • 트랜잭션이 commit을 만날 때까지 lock을 갖고 있다가 commit을 만날때 unlock을 하는 방식으로 데드락이 발생하지만 병행성이 좋음
    • 일반적으로 병행성이 좋은 strict 방식을 사용합니다.





6. MySQL InnoDB 엔진의 트랜잭션과 Locking

지금까지 트랜잭션의 ACID 특징에 대해 알아보았습니다.

RDB에서는 일반적으로 잠금 스케줄러를 사용하는데, 충분히 좋은 성능을 낼 수 있다고 합니다.


지금까지 쿼리에서 transaction을 사용하면 자동으로 locking 처리가 되는 줄 알았는데, 제품에 따라 또는 설정에 따라 다른것 같습니다.

아래는 MySQL InnoDB 엔진의 트랜잭션 잠금 스케줄러에 대한 참고 자료입니다.

  • transaction-isolation-levels에 대한 내용
    • mysql 공식 문서 ( 링크 )
    • InnoDB 엔진의 기본 Isolation Level로 인한 장애 예방 경험 글 ( 링크 )
  • transaction 셋팅
    • mysql 공식 문서 ( 링크 )





이상으로 트랜잭션에 대해 알아보았습니다.

트랜잭션은 DB개념에 있어서 중요한 개념이며, 특히 ACID 특성에 대해서 잘 알아두어야 합니다.