이번에는 Database의 Transaction에 대해서 다루어 보려고 한다.
Transaction은 데이터 베이스의 상태를 변경시키는 여러 읽기/쓰기 등 작업을 하나의 논리적인 작업으로 묶음을 뜻한다.
상태를 변경 시킨다는 뜻은 우리가 SQL을 사용하여 Database에 어떠한 명령을 내린다는 뜻이다.
그렇다면 여러 작업을 하나의 논리적인 작업으로 묶는다는게 무슨소리일까?
유명한 예시를 보자
우리는 은행 시스템을 만들고 있고 A라는 사람이 B라는 사람에게 돈을 송금하는 작업을 개발해야한다.
위 기능에 총 데이터베이스 작업은 3가지 이다.
1. A사람의 돈이 송금할 금액만큼 있는지 확인한다.
2. A사람의 송금할 돈을 차감한다.
3. B사람의 기존 돈에 송금된 돈을 더한다.
만약 1,2번 작업을 수행한 뒤 이유모를 원인으로 3번 작업 진행중 실패한다면 어떻게 해야할까?
이러한 문제가 생기면 개발자가 직접 A사람의 돈을 복구시키는 보상조치를 해줘야 할것이다.
만약 위 모든 작업을 하나의 작업으로 묶어 3번 작업을 실패하면 알아서 2번 작업이 취소되면 얼마나 좋을까???
Transaction은 1,2,3번 작업을 하나의 논리적 작업으로 묶어 3번에서 실패하는 경우 2번까지 같이 복구해준다. 이를 Transaction의 Atomic(원자성)이라고 부른다.
트랜잭션의 consistency는 트랜잭션이 완료된 결괏값이 일관적인 DB 상태를 유지하는 것을 말한다.
일관된 상태의 데이터베이스에서 하나의 트랜잭션을 성공적으로 완료하고 나면 그 데이터베이스는 여전히 일관된 상태여야 한다. 즉, 트랜잭션 수행 전과 후에 데이터 모델의 모든 제약 조건(기본 키, 외래 키, 도메인, 도메인 제약조건 등)을 만족하는 것을 통해 보장한다.
이번에는 다른 상황의 예시를 들어보겠다.
하루에 송금이 가능한 횟수는 3번이다.
만약 이미 A유저는 2번을 송금한 상태에서 동시에 2개의 송금 트랜잭션이 시작되면 어떻게 될까????
우리는 이와 같이 여러 작업이 같은 데이터에 접근하려는 상황에서 생기는 문제를 Race Condition(경쟁 상태)라고 한다.
데이터 베이스는 이러한 경쟁상태를 대비하기 위해 실행 중인 트랜잭션의 중간결과를 다른 트랜잭션이 접근할 수 없도록 막는다. 이를 Transaction의 Isolation(격리성) 이라고 부른다.
먼저 격리성을 지원하지 않을때 생길 수 있는 문제들에 대해서 알아보겠다.
이런 문제들을 방지하고자 Transaction의 4가지 격리 수준을 지원한다.
격리 수준에 대한 내용은 아래에서 다루겠다.
트랜잭션의 Durability(영속성)은 커밋이 완료된 트랜잭션의 내용은 영구적으로 유지되도록 보장하는 속성이다.
시스템의 오류, 충돌, 전원 손실과 같은 예상치 못한 상황이 발생해도 완료된 트랜잭션의 결과가 보존되도록 보장된다.
그러면 어떻게 영속성을 보장할까??
모두 이해하지 못했습니다. ㅠㅠ.... 글 마지막 링크를 봐주세요 ㅠ
Transaction에는 4가지 Isolation Level(격리 수준)이 존재한다.
이 수준에서는 한 트랜잭션이 커밋되지 않은 변경 사항을 다른 트랜잭션이 볼 수 있다. "Dirty Read" 현상을 허용하는데, 이는 한 트랜잭션이 아직 완료되지 않은 다른 트랜잭션의 변경사항을 읽을 수 있음을 의미한다. 데이터 일관성에 문제가 있어 실제로 사용하는지는 잘 모르겠다.
Commit된 데이터만 읽을수 있고 Commit되지 않은 데이터는 읽지 않는다.
하나의 트랜잭션이 데이터를 수정한 상태로 작업 진행중인 상황에서 다른 트랜잭션이 읽으려고 접근한다면 수정되지 않은 기존의 데이터만 읽을 수 있다.
Write는 Lock이 걸려있는 상태이다.
이 수준의 격리레벨을 사용한다면 "Dirty Read"의 문제는 발생하지 않지만 "Non-Repeatable Read" 현상은 발생될 수 있다.
트랜잭션이 진행되는동안 데이터가 변경되더라도 원본 데이터를 읽게 만든다.
즉 읽는 시점에서의 특정 버전에 해당하는 데이터만 읽는다고 생각하면 될듯 하다.
이 수준의 격리 레벨에서는 Non-Reapeatable Read는 발생하지 않지만 범위 잠금은 관리되지 않으므로 "Phantom Read" 발생할 수 있다.
그리고 Repeatable Read수준에서는 Write skew, lost update가 발생될 수 있다.
(lost update같은 경우에는 atomic연산, 명시적잠금 방법으로 해결할 수 있다.)
이 수준은 가장 높은 격리 수준으로, 트랜잭션이 다른 트랜잭션의 결과에 전혀 영향을 받지 않도록 보장한다.
모든 트랜잭션을 줄세워 할거같지만 실제로는 그렇지는 않는다.
시스템마다 조금씩 다르겠지만 Where문을 확인하여 range locking 또는 row locking 같은 강력한 locking전략을 사용한다고 한다.
직렬화 가능 수준에서는 트랜잭션이 완전히 독립적으로 실행되며, 이는 데이터의 일관성과 무결성을 최대화하지만 동시성이 크게 감소하여 성능에 부정적인 영향을 줄 수 있다.
위에서 소개한 Transaction 4가지 특성을 우리는 ACID라고 한다.
위와 같은 트랜잭션의 속성이 없다면 개발자가 직접 어플리케이션 단에서 관리 해줘야 한다. (실제로 그래야하는 일이 있음ㅠ....)
Database를 사용하는 개발자라면 Transaction이 뭔지 내가 사용하는 DBMS의 기본 isolation lelvel은 뭔지 반드시 알아야 한다고 개인적으로 생각한다.
내 비즈니스 코드는 모두 완벽한거 같은데 왜 DB에 이상한 일이 생기는거지??? 하면 범인은 바로 Transaction일 것이다.
Transaction에 대해서 정리해 봤다. 아직 부족한 내용들이 있다.
Transaction을 정리하고 Distributed Transaction을 정리할 생각이였지만 시간이 걸릴듯 하다.
정리하면서 느끼는것이 나는 알고있다고 착각하고 있었다는 것이다.
머릿속의 파편된 내용들을 글로 정리해도 이렇게 어렵고 모르는것이 많은데
내가 제대로 알고 있는게 있나 싶기도 하다.