1. 트랜잭션(Transaction)이란?
트랜잭션이란 보통 은행 ATM이나 데이터베이스 등의 시스템에서 사용되는 더 이상 쪼갤 수 없는 업무 처리의 최소 단위라고들 설명을 한다.
그렇다면 더 이상 쪼갤 수 없는 업무처리의 최소단위의 기준은 무엇일까?
만약 A라는 사람이 B라는 사람에게 1,000원을 지급하고 B가 그 돈을 받은 경우를 생각해보자.
위 예시를 실행하기 위해선 어떤 작업이 진행될까?
- A의 잔고를 확인 하는 select 작업 진행
- A의 잔고가 1000원이 넘는다면 A의 잔고를 -1000 하는 update가 진행
- 마지막으로 B의 잔고를 +1000 하는 update 가 진행
아마도 이렇게 세 단계를 거치게 될 것이다.
만약 A는 돈을 지불했으나 B는 돈을 받지 못했다면 그 거래는 성립되지 않는다.
이처럼 A가 돈을 지불하는 행위와 B가 돈을 받는 행위는 별개로 분리될 수 없으며 하나의 거래내역으로 처리되어야 한다.
즉, 금융 거래의 경우에는 송금이라는 단위 작업이 복수의 단계로 이루어질 수 있지만, 이러한 단계들을 묶어서 하나의 트랜잭션으로 간주하게 된다.
그래서 트랜잭션이란 더 이상 쪼갤 수 없는(분리될 수 없는) 업무처리의 최소 단위라고 말하는 것이다!
그런데, 만약 2단계까지는 성공을 했는데, 3단계가 진행되기 직전에 B의 계좌가 삭제되어 받을 수 없다면? 이럴 경우엔 어떻게 될까?
이럴 경우를 방지하기 위하여 트랜잭션에는 commit과 rollback이 존재한다.
- commit : 트랜잭션 처리가 정상적으로 완료된 경우 '트랜잭션을 커밋한다.'라고 한다.
- rollback : 오류가 발생할 경우 '트랜잭션을 원래 상태대로 롤백한다.' 라고 한다.
만약 3단계가 진행되기 전에 오류가 발생해 B가 돈을 받지 못했다면, 트랜잭션은 롤백이 되고, 다시 A의 계좌도 원래 상태로 돌아가게 된다.
이렇듯 트랜잭션은 커밋, 롤백 둘 중 하나가 무조건 진행이 되며, 커밋, 롤백이 진행되기 위해선 특정 조건들을 만족시켜야한다.
어떤 조건들이 있는지 다음 챕터에서 알아보도록 하자!
2. 트랜잭션의 특징 (ACID)
트랜잭션이 제대로 실행되기 위해선 특정 조건들이 존재하는데, 이것을 트랜잭션의 특징이라고 부른다.
원자성(Atomicity), 일관성(Consistency), 격리성(Isolation), 지속성(Durability)의 네 가지가 있으며, 앞글자를 따서 ACID라고 한다.
각 특징이 어떤 내용을 담고 있는지 살펴보도록 하자!
- 원자성(Atomicity) : 트랜잭션을 "모두 실행되거나 아무것도 실행되지 않는다"는 원자적인 단위로 간주한다는 특징이다.
즉, 모든 작업이 성공적으로 완료되거나 아무런 작업도 수행되지 않은 상태로 유지되어야 한다. (All or Noting)
만약 한 트랜잭션이 실행될 때, 중간에 어떠한 이유로 인해 실패한다면, 지금까지의 작업을 모두 취소하여 아무 일도 없었던 것처럼 rollback되어야 한다는 뜻이다.
예를 들어, 은행 송금 트랜잭션에서 송금과 수신 작업은 모두 성공하거나 모두 실패해야 한다. - 일관성(Consistency) : 트랜잭션이 진행된 전후의 데이터베이스는 일관된 상태를 유지해야 한다는 특징이다.
즉, 트랜잭션이 실행되기 전과 후에 데이터베이스의 무결성 규칙이 유지되어야 한다.
예를 들어, 은행 송금 후에 발생하는 잔고의 합은 변하지 않아야 합니다. - 격리성(Isolation) : 복수의 트랜잭션이 동시에 실행될 때, 각 트랜잭션은 다른 트랜잭션에 영향을 받지 않아야 한다는 특징이다.
즉, 여러 트랜잭션들이 동시에 실행될 때도 각각의 트랜잭션은 혼자 실행되는 것처럼 동작하게 만드는 것이며, 이것은 각 트랜잭션과 트랜잭션 사이의 간섭이 없어야 함을 의미한다. - 지속성(Durability) : 트랜잭션이 성공적으로 완료되면 그 결과는 영구적으로 반영되어야 한다는 특징이다.
시스템이 중단되거나 장애가 발생하더라도 트랜잭션이 완료된 후에는 데이터베이스에 반영된 변경 사항이 유지되어야 한다.
즉, 위 특징들을 하나의 예시로 묶으면 아래와 같다!
은행 송금 트랜잭션에서, 트랜잭션이 송금 금액을 A 계좌에서 차감하고, 동시에 수신 금액을 B 계좌에 추가하는 작업을 할 때 이 트랜잭션은 원자성을 준수하여 전체가 성공하거나 실패하게된다. (원자성)
또한, 송금 전과 후에는 A계좌의 합과 B계좌의 합이 일관된 상태를 유지해야 하며, (일관성)
이 트랜잭션이 진행될 땐 다른 트랜잭션에 영향을 받지 않아야 한다. (격리성)
마지막으로, 송금이 완료되면 이 변경 사항은 데이터베이스에 영구적으로 저장되어야 한다. (지속성)
이 특징들은 데이터베이스의 트랜잭션이 안전하게 수행되기 위한 4가지 필수적인 성질이기 때문에 매우 중요하다!
하지만 실제로는 ACID원칙은 종종 지켜지지 않는다. 왜냐하면 ACID원칙을 정확하게 지키려면 동시성이 매우 떨어지기 때문이다.
그렇기 때문에 DB엔진은 ACID원칙을 희생하여 동시성을 얻을 수 있는 방법을 제공한다.
이 방법은 위 특징들 중 격리성(Isolation)과 관련이 있다.
격리성(Isolation)은 고립이라고도 하는데 이것은 여러 트랜잭션이 동시에 실행될 때,
각각의 트랜잭션이 다른 트랜잭션의 작업에 영향을 받지 않고 독립적으로 실행되는 것을 보장한다.
이것을 가능하게 하려면 여러 트랜잭션이 동시에 접근하는 데이터에 대한 제어가 필요한데, 이는 주로 격리수준(Isolation Level)을 통해 제어된다.
Isolation Level은 따로 챕터를 빼놨는데, 이를 쉽게 이해하기 위해선 다시 부정합문제(이상현상)에 대해 알아야한다.
먼저 부정합문제에 대해 알아보자!
3. 부정합문제(이상현상)
1. Dirty Read
더티 리드는 커밋되지 않는 변화를 읽는 것을 말한다.
예를 들어, Tx1과 Tx2가 있고, x = 10, y = 20이라는 데이터가 있다고 가정해보자.
Tx1은 x에 y를 더하는 작업을 하고, Tx2는 y를 70으로 바꾸는 작업을 한다.
- Tx1이 실행되어 x = 10을 읽는다.
- 이 때, Tx2가 y를 70으로 바꾸는 작업을 수행한다.
- Tx1이 y = 70이라는 값을 읽는다.
- Tx1이 x에 y를 더하는 작업을하여 x = 80이 된다.
- Tx1을 커밋한다.
- 이 때, Tx2를 어떠한 문제로 인해 rollback시켰다.
위 과정을 거치면서 Tx1과 Tx2 둘 다 작업을 마쳤다.
이 때, 생각해볼 문제는 마지막 6번의 과정이다. 6번을 거치며 y는 다시 20이 되었다.
그러나 x는 여전히 80이다. Tx1은 롤백되지 않았기 때문이다.
Tx2가 롤백이 되었기 때문에 y = 70이라는 값은 유효하지 않은 값이고, 때문에 x = 80이라는 것은 이상한 값이 되는 것이다.
바로 이런 문제를 Dirty Read라고 부른다.
Tx1이 커밋되지 않은 Tx2을 읽었기 때문에 이러한 이상현상이 발생하는 것이다.
2. non-Repeatable Read
논리피터블리드는 한 트랜잭션 내에서 반복 읽기를 수행하면 다른 트랜잭션의 커밋 여부에 따라 조회 결과가 달라지는 문제이다.
예를 들어, Tx1과 Tx2가 있고, x = 10이라는 데이터가 있다고 가정해보자.
Tx1은 x를 두 번 읽는 작업을 하고, Tx2는 x에 40을 더하는 작업을 한다.
- Tx1이 실행되어 x = 10을 읽는다.
- Tx2가 실행되어 x에 40을 더한다.
- Tx2를 커밋한다.
- Tx1이 다시 x를 읽어 x = 50을 읽게된다.
- Tx1을 커밋한다.
위 과정을 거치면서 Tx1과 Tx2 둘 다 작업을 마쳤다.
이 때, 생각해볼 문제는 4번의 과정이다. Tx1이 작업을 하면서 x를 총 두 번을 읽었는데,
첫번째 읽었을 땐 x = 10을 읽었고, 두 번째 읽었을 땐 x = 50을 읽었다.
같은 데이터를 한 트랜잭션 안에서 두 번 읽었음에도 불구하고 서로 다른 값을 조회하게 된것이다.
바로 이런 문제를 Non-Repeatable Read라고 부른다.
만약 Tx2가 x에 40을 더한 후 커밋하지 않은 상태에서 Tx1이 x를 한 번 더 읽었다면 여전히 x는 10일 것이다.
Non-Repeatable Read는 이렇게 같은 데이터를 반복해서 읽었을 때, 커밋여부에 따라 읽는 데이터가 달라질 수 있는 문제이다.
3. Phantom Read
팬텀리드는 한 트랜잭션 내에서 없던 데이터가 생기는 것 문제를 말한다.
예를 들어, Tx1과 Tx2가 있고, x = 30, y = 10이라는 데이터가 있다고 가정해보자.
Tx1은 값이 30인 데이터를 읽는 작업을 두 번 하고, Tx2는 y에 20을 더하는 작업을 한다.
- Tx1이 실행되어 값이 30인 데이터 x를 읽는다.
- Tx2가 실행되어 y에 20을 더한다.
- Tx2를 커밋한다.
- Tx1이 값이 30인 데이터 x, y를 읽는다.
- Tx1을 커밋한다.
위 과정을 거치면서 Tx1과 Tx2 둘 다 작업을 마쳤다.
이 때, 생각해볼 문제는 4번의 과정이다. Tx1이 작업을 하면서 x를 총 두 번을 읽었는데,
첫번째 읽었을 땐 x만을 읽었고, 두 번째 읽었을 땐 x,y를 읽었다.
같은 데이터를 한 트랜잭션 안에서 두 번 읽었을 때 없던 데이터(유령 데이터)가 생기게 된것이다.
바로 이런 문제를 Phantom Read라고 부른다.
만약 Tx3가 중간에 새로운 데이터 z = 30을 insert한 후 커밋을 했을 경우에도 이 Phantom Read가 발생할 수 있다.
여기까지 이상현상에 대해 알아봤다.
이런 이상한 결과, 데이터 불일치는 당연히 발생하지 않는 것이 좋다.
우리는 이런 이상한 현상들이 모두 발생하지 않게 설정할 수 있다.
그러나 그렇게 되면 제약사항이 많아져 동시처리 가능한 트랜잭션 수가 줄어들어 결국 DB의 전체 처리량이 하락하게 된다.
이 문제를 해결하기 위해 일부 이상한 현상을 허용하는 몇 가지 레벨을 만들어서 사용자가 필요에 따라서 선택할 수 있도록 적용한것이 Isolation Level이다.
Isolation Level은 이상현상을 얼마나 허용하는지에 따라 레벨이 나뉘어진다.
다음 챕터에서 Isolation Level에 대해 알아보자!
4. Isolation Level (트랜잭션 격리수준)
트랜잭션 격리수준이란 동시에 여러 트랜잭션이 처리될 때, 특정 트랜잭션이 다른 트랜잭션에서 변경하거나 조회하는 데이터를 볼 수 있도록 허용할지 말지를 결정하는 것을 말한다.
격리수준은 데이터베이스 관리시스템에서 설정할 수 있는 다양한 옵션 중 하나로, 각각의 수준은 트랜잭션 간에 얼마나 강렬한 격리를 제공하는지를 나타낸다.
격리수준은 크게 4개로 나뉘어진다.
- Read Uncommitted
- Read Committed
- Repeatable Read
- Serializable
아래로 내려올수록 트랜잭션 간 고립 정도가 높아지며, 반대로 성능은 떨어지는 것이 일반적이다.
1. Read Uncommitted
이 격리 수준은 Dirty Read, Non-Repeatable Read, Phantom Read를 모두 허용한다.
좋게 말하면 가장 자유로운 것이고, 나쁘게 말하면 이상현상에 가장 취약한 레벨이다.
대신 동시성은 높아져서 전체 처리량은 가장 높다.
즉, 커밋되지 않은 상태의 다른 트랜잭션을 모든 트랜잭션이 자유롭게 데이터를 읽을 수 있다.
2. Read Committed
이 격리 수준은 이름에서도 알 수 있듯이 커밋된 데이터만 읽기 때문에 Dirty Read는 허용하지 않고,
Non-Repeatable Read, Phantom Read 는 허용하는 레벨이다.
즉, Tx1이라는 트랜잭션이 데이터를 커밋한 후에만 다른 트랜잭션들이 해당 데이터를 읽을 수 있다.
따라서 Dirty Read는 발생하지 않지만, 트랜잭션이 데이터를 변경하고 커밋한 후에는 다른 트랜잭션에서 해당 내용을 읽을 수 있다.
3. Repeateble Read
Dirty Read, Non-Repeatable Read는 허용하지 않고 Phantom Read는 허용하는 레벨이다.
즉, 이 격리 수준은 트랜잭션이 읽은 테이터가 동일한 트랜잭션이 커밋될 때 까지 변경되지 않음을 보장한다.
그러나 Phantom Read는 허용하는데, 이는 트랜잭션의 rock과 관련이 있다. ( rock에 관한 부분은 이번 포스팅에서는 다루지 않는다.)
4. Serializable
Dirty Read, Non-Repeatable Read, Phantom Read를 모두 허용하지 않는 레벨이다.
또한, 위 세가지 이상현상뿐만 아니라 모든 이상현상을 허용하지 않는다.
이 격리 수준은 다른 격리 수준보다 더 많은 락 충돌이 발생할 가능성이 높으며, 이는 DB시스템의 성능에 영향을 줄 수 있어 실제로는 잘 사용하지 않는다.
레벨이 높아질수록 허용하지 않는 현상들이 늘어나면서, 동시성은 떨어지게 된다.
위 내용을 간단하게 표로 정리하면 아래와 같다.
Isolation Level | Dirty Read | Non-Repeatable Read | Phantom Read |
Read Uncommitted | ⃝ | ⃝ | ⃝ |
Read Committed | ⃝ | ⃝ | ✕ |
Repeatable Read | ⃝ | ✕ | ✕ |
Serializable | ✕ | ✕ | ✕ |
**mysql은 default가 repeatable read, oracle은 read committed가 default값임
항상 헷갈려서 정리해야지 하고 미뤘던 트랜잭션 특징과 아이솔레이션 레벨!
사실 아이솔레이션 레벨을 깊이 있게 정리해보려고 했으나, 공부하면 공부할수록 너무 많은 내용이 나왔다ㅠㅠ
그래서 아이솔레이션을 본격적으로(?) 공부하기 전에 이상현상에 대해 알면 추후 아이솔레이션을 더 쉽게 이해할 수 있을 것 같아 이상현상에 대한 내용도 추가하게 되었다.
그래서 다음번에는 아이솔레이션을 더 깊이 있게 이해할 수 있도록 DB락에 대해 포스팅해보고, 다시 한 번 아이솔레이션 레벨에 대해 깊이 있는 내용을 다뤄봐야겠다.
댓글