트랜잭션이 뭘까? 이게 그렇게 중요한 건가? 궁금증을 해결하기 위해 이 포스팅을 쓰게 됐습니다.
1. 트랜잭션이란?(What is Transaction?)
· 데이터베이스의 상태를 변환시키는 하나의 논리적인 작업 단위를 구성하는 연산들의 집합이다.
도대체 트랜잭션이 뭘까요?? 위의 형식적인 정의는 읽을 수록 헷갈리고 무슨 말하는지도 몰라서 간단하게 정리해보겠습니다. 간단하게 말해서 아래의 SQL 질의문으로 DB 에 접근하는 것을 의미합니다.
● SELECT
● DELETE
● UPDATE
● INSERT
너무 간단하지 않나요? 저것만 하면 트랜잭션을 한거라구요? 당연히 아닙니다. 트랜잭션은 하나의 SQL 문이 아니라 많은 SQL 질의문을 묶어 관리자나 개발자가 하나의 단위로 정의한 것입니다.
게시판을 예로 한 번 들어보겠습니다. 내가 글을 올리고 글이 잘 올라갔는지를 확인하는 과정을 하나의 트랜잭션이라고 볼 수 있습니다. 글을 올린다(INSERT) -> 잘 올라갔는지 확인한다(SELECT) 의 로직으로 구성됩니다. INSERT 와 SELECT 를 합친 것이 작업의 단위라고 볼 수 있고 이것이 트랜잭션 입니다.
그렇다면 트랜잭션이 필요한 이유는 무엇일까요? 전자상거래 로직으로 설명해보겠습니다.
전자상거래 로직은 하나의 쿼리로 표현할 수 없는 예제입니다.
상품 구매전 잔여금액을 확인하고 잔여금액이 선택상품의 가격보다 많으면 선택상품구매 로직으로 넘어간다. 그리고 선택상품의 재고가 있다면 잔여금액에서 선택상품의 가격만큼을 내리고 로직을 종료한다.
로직만 본다면 완벽한 것 같지만 허점이 존재합니다.
① 선택상품구매 로직에서 Exception()이 발생하여 상품이 없음에도 있다고 처리가 된 경우.
② 잔여금액이 감소하는 과정에서 서버가 종료되어 상품은 구매했지만 잔여금액은 감소하지 않는 상황.
이러한 허점은 엄청난 손해를 발생시킬 것이고 누구도 이 전자상거래를 이용하고 싶지 않을 것입니다.
이것을 해결해 줄 수 있는 것이 바로 트랜잭션 기술입니다. 2개 이상의 쿼리를 하나의 커넥션으로 묶어서 DB에 전송하여 에러가 발생할 경우 Rollback 시키는 것입니다. All Or Nothing 전략이라고 볼 수 있습니다.
즉, 트랜잭션은 데이터의 일관된 상태를 유지하기 위해 사용되는 것입니다.
※ 깨알 용어 설명
Commit : 한개의 논리적 단위(트랜잭션)에 대한 작업이 성공적으로 끝나 데이터베이스가 다시 일관된 상태에 있을 때, 이 트랜잭션이 행한 갱신 연산이 완료된 것을 트랜잭션 관리자에게 알려주는 연산이다.
RollBack : 하나의 트랜잭션 처리가 비정상적으로 종료되어 데이터베이스의 일관성을 깨뜨렸을 때, 이 트랜잭션의 일부가 정상적으로 처리되었더라도 트랜잭션의 원자성을 구현하기 위해 이 트랜잭션이 행한 모든 연산을 취소(Undo)하는 연산이다.
2. 트랜잭션의 특징 (Transaction's property)
- 원자성 (Atomicity)
- 일관성 (Consistency)
- 독립성 (Isolation)
- 지속성 (Durability)
원자성은 트랜잭션이 데이터베이스에 모두 반영되던가, 아니면 전혀 반영되지 않아야 한다는 것.
일관성은 트랜잭션의 작업 처리 결과가 항상 일관성이 있어야 한다는 것
독립성은 둘 이상의 트랜잭션이 동시에 병행 실행되고 있을 경우에 어느 하나의 트랜잭션이라도 다른 트랜잭션의 연산을 끼어들 수 없다.
지속성은 트랜잭션이 성공적으로 완료됬을 경우, 결과는 영구적으로 반영되어야 한다는 점이다.
3. 트랜잭션의 격리 수준(Transaction Isolation Level)
격리 수준에 대한 이야기를 하기에 앞서서 몇 가지 알고 넘어가야할 점이 있습니다.
▶ 트랜잭션의 동시성
동시성이 뭘까요? 말 그대로 동시에 실행되어야한다는 의미 같은데...
트랜잭션에서 동시성이란 여러 사용자들이 하나의 데이터베이스에 접근했을 때 적절한 통제 조치가 있어야 한다는 것을 말합니다.
여러 사람이 하나의 데이터를 지우고 쓰고 업데이트하고.... 만약 통제 조치가 없다면 데이터의 무결성이 깨지게 되고 신뢰성이 없는 데이터들로 꽉 차게 될 것입니다. 따라서 DBMS는 동시성 제어 기능을 제공하여 DB의 무결성을 보호하고 사용자들이 항상 정확하고 일관된 데이터를 참조할 수 있게 해줘야 합니다.
그렇다면 트랜잭션이 들어오는 일련의 순서대로 처리하면 동시성 문제가 없어지지 않을까요? 이런 생각을 하실 수도 있지만 그렇게 된다면 DB의 성능은 크게 떨어지게 됩니다. 동시성 없이 처리하니까 응답성도 느릴 것입니다.
▶ Locking
트랜잭션들 간에 동일한 데이터에 대한 동시 접근을 제한하기 위하여 Lock을 설정하여 관리합니다. 여기서 Lock 은 Thread 에서 공유 자원에 동시 접근을 제한하기 위해 Lock을 거는 것과 비슷합니다. 어쨌든 트랜잭션에 Lock을 걸수록 그만큼 동시성은 낮아지고 응답성이 낮아집니다. 따라서 상황별로 꼭 필요한 수준의 Lock을 걸어서 트랜잭션의 원자성을 유지하고 최대한 성능을 내기 위해서는 동시성 이슈가 발생할 수 있는 상황들을 정확히 구분하는 것이 매우 중요합니다. 여기서 나온 구분 기준이 격리 수준(Isolation level) 입니다.
우선 각 격리 수준을 알아보기전에 트랜잭션의 격리 수준에 따라 일어날 수 있는 바람직하지 않은 Read를 알아보겠습니다.
① Dirty Read
Commit 전 Data Read를 하는 경우를 말한다.
트랜잭션 T1이 x 값을 10으로 바꾸고 커밋을 하지 않은 상태에서 다른 트랜잭션인 T2가 x 값을 읽는 상황.
② Non-repeatable Read
x=10 이 있다고 가정하자.
트랜잭션 T1이 x를 읽고 트랜잭션 T2 가 x를 20으로 바꿨다. 다시 T1 이 x를 읽었을 때 그 값은 20으로 바뀌어 있다. 이 상황에서 T1은 값이 바뀌었기 때문에 반복적 읽기를 할 수가 없다.
③ Phantom Read
트랜잭션 T1으로 특정 조건 C로 검색하여 2개의 row를 얻었다. 이 때 다른 트랜잭션 T2가 테이블에 접근해 C조건의 데이터를 추가했을 때, 아직 Commit 하지 않은 T1이 다시 조건 C로 검색하면 총 3개의 데이터를 얻는다. 데이터가 추가된 것이다. 이를 Phantom Read라 한다.
총 3개의 경우는 격리 수준에 따라 모두 나타날 수도 모두 안 나타날 수도 있습니다.
이제 격리 수준(Isolation Level)을 알아보겠습니다.
가장 낮은 isolation 레벨 0의 경우 lock이 걸리지 않기때문에 속도는 매우 빠르나 동시 접근을 허용하기때문에 데이터 정합성에 문제가 생길 수 있고, 반대로 가장 높은 레벨3의 경우 완전히 lock을 걸어 동시 접근을 차단하고 순차적으로 처리 (serializable) 하기 때문에 원자성은 완벽하지만 동시에 처리 할 수 있는 양이 적어 속도가 매우 느립니다.
(1) Read Uncommited - level 0
한 트랜잭션에서 커밋하지 않은 데이타에 다른 트랜잭션이 접근 가능하다. 즉, 커밋하지 않은 데이타를 읽을 수 있다.
(2) Read Comitted - level 1
커밋이 완료된 데이타만 읽을 수 있다.
(3) Repeatable Read - level 2
트랜잭션 내에서 한번 조회한 데이타를 반복해서 조회해도 같은 데이타가 조회 된다
(4) Serializable - level 3
가장 엄격한 격리 수준, 모든 트랜잭션에 Lock 을 건다.