데이터베이스 트랜잭션(Database Transaction)
데이터베이스 트랜잭션이란?
데이터베이스 트랜잭션(Database Transaction)은 데이터베이스 관리 시스템 또는 유사한 시스템에서 상호작용의 단위이다. 여기서 유사한 시스템이란 트랜잭션이 성공과 실패가 분명하고 상호 독립적이며, 일관되고 믿을 수 있는 시스템을 의미한다.
이론적으로 데이터베이스 시스템은 각각의 트랜잭션에 대해 원자성(Atomicity), 일관성(Consistency), 독립성(Isolation), 영구성(Durability)을 보장한다. 이 성질을 첫글자를 따 ACID라 부른다. 그러나, 실제로는 성능향상을 위해 이런 특성들이 종종 완화되곤 한다.
어떤 시스템들에서는 트랜잭션들은 논리적 작업 단위(LUW, Logical Units of Work)로 불린다.
- 위키백과 (데이터베이스 트랜잭션)
중고 물품 직거래를 하는 상황을 상상해보자. 과정은 아래와 비슷할 것이다.
위 과정 중 하나라도 이루어지지 않는다면 직거래는 바로 끝(또는 PATO)날 것이다. 그리고 다시 새로운 거래 상대를 찾아 거래를 진행할 것이다.
반면에 모든 과정이 정상적으로 이루어진다면 직거래는 깔끔하게 끝날 것이다.
트랜잭션이 이렇다.
트랜잭션은 데이터베이스의 상태에 어떤 변화가 일어날 때 이 변화의 과정을 모아놓은 것이다.
단순하게는 블로그에 글을 써서 저장을 누르면 데이터베이스에 데이터가 들어가는 경우가 있을 것이고,
좀 더 복잡해지면 은행 송금처럼 보내는 사람과 받는 사람의 통장 유효 여부부터 잔액 확인 및 입·출금 처리 완료 확인까지가 한 묶음인 경우도 있을 것이다.
트랜잭션의 성질 (ACID)
트랜잭션의 성질이자 조건으로서 ACID로 표현되는 4가지가 있다.
Atomicity(원자성), Consistency(일관성), Isolation(고립성), Durability(지속성)이다.
1) Atomicity (원자성)
원자(原子, atom)는 일상적인 물질을 이루는 가장 작은 단위이다. 일상적인 물질들이 원소로 구성되어 있기 때문에, 이는 화학 반응을 통해 더 쪼갤 수 없는 단위와 동의어이다.
- 위키백과 (원자)
즉, 더 이상 쪼개어질 수 없는 성질이다(원자를 핵반응으로 더 쪼갤 수 있다고는 하는데, 여기서는 침묵하겠다).
하나의 트랜잭션은 그 자체로서 완전한 하나이다.
그 안에 아무리 많은 쿼리와 여러 과정들이 있더라도 트랜잭션에 속한 이상 하나라도 죽으면 다 같이 죽고, 모두가 살아남아야만 최종 목표(데이터베이스 조작)를 달성할 수 있다.
한 마디로 모 아니면 도, 0 아니면 1, All or Nothing이다.
2) Consistency (일관성)
자식에게 국가별 식사 예절을 가르치려는 부모가 있었다.
이 부모는 아이에게 한국식 식사 예절과 프랑스식 식사 예절을 가르쳤다.
한국에 있을 때 한국식 예절을 지키면 칭찬을 해줬고, 어기면 혼을 내거나 교정을 해줬다. 프랑스에서도 동일하게 하였다.
그 결과 아이는 한국에서는 한국식으로, 프랑스에서는 프랑스식으로 식사 예절을 잘 지키게 되었다.자식이 있는 또 다른 부모가 있었다.
위 이야기의 부모처럼 아이에게 한국식 그리고 프랑스식 식사 예절을 가르쳤다.
그러나 아이가 식사 예절을 지키면 어느 때는 칭찬을 하고, 어느 때는 안했다. 예절을 안 지키면 어느 때는 혼을 내고, 어느 때는 내지 않았다. 프랑스에서도 동일하게 하였다.
그 결과 아이는 언제 어디서 어떤 식사 예절을 지켜야 할지 혼란스러웠고, 결국 한국과 프랑스 어딜 가든 식사 예절이 없는 아이가 되었다.
트랜잭션의 일관성은 트랜잭션이 수행이 완료된 후에도 데이터베이스가 일관된 상태를 유지하는 것을 의미한다. 이를 위해 데이터베이스 시스템의 고정적인 요소는 트랜잭션 전후 모두 동일해야 하며, 데이터베이스의 여러 가지 규칙과 제약을 지키는 유효한 데이터만 데이터베이스에 기록되어야 한다.
여기서 짚고 넘어갈 점은, 트랜잭션이 수행되는 도중에 데이터베이스의 내용이 변하면 트랜잭션의 결과는 변동 후의 데이터베이스가 아닌 트랜잭션이 시작된 순간, 즉 변동 전의 데이터베이스를 기준으로 적용된다.
변동 전 내용대로 일처리를 했는데 결과는 변동 후 내용을 기준으로 하라고 하면 어긋나는 부분이 생기기 때문이다.
각각의 트랜잭션이 시작부터 종료까지 자신에게 주어진 일관된 규칙을 따르므로, 그 결과 데이터베이스는 어긋남이 없이 일관된 상태를 유지할 수 있다.
한국식으로 식사를 시작했으면 식사를 마칠 때까지 한국식으로 먹는 것이다.
식사 도중에 마법에 걸려 장소가 갑자기 프랑스로 바뀌었으면 다음 식사부터 프랑스 식으로 하면 되는 것이다.
단, 식사 방식에 맞는 예절을 항상 지켜야 한다.
3) Isolation (고립성)
위에서 나온 식사 예절을 잘 지키는 가족을 다시 떠올려보자.
이 가족이 오랜만에 레스토랑에 외식을 하러 나왔다.
레스토랑이다보니 다른 여러 가족들도 있다.
편한 자리를 잡고 음식을 주문하여 이제 식사를 시작하려고 한다.
그런데 이때 식사 예절이 최악인 어느 가족이 이들의 테이블에 난입했다!
그리고 허락도 없이 예절 있는 가족의 음식을 집어먹으며 자리를 잔뜩 어지럽혔다.
그렇게 식사를 망쳤다.지난번의 경험으로 인해 이번에는 칸막이가 있는 레스토랑으로 외식을 나왔다.
칸막이 덕분에 다른 사람도 보이지 않고, 방해하는 사람도 없어 아주 깔끔하게 식사를 했다.
트랜잭션의 고립성은 칸막이 있는 레스토랑에서의 식사와 같다.
식사를 하는 와중에 다른 가족들 누가 있는지 안 보이고 방해도 받지 않는 것처럼, 트랜잭션도 다른 트랜잭션들과 완전히 분리되어 자신의 할 일을 수행한다.
또한 현재 어떤 테이블에서 식사 중인 사람들이 식사를 완전히 다 마치고 자리를 뜨고 나서야 다음 사람들이 그 테이블에 가서 식사를 할 수 있듯이, 어떠한 대상에 대해 여러 트랜잭션이 작업을 해야 할 경우 그 대상에 도착한 순서대로 작업을 수행하고, 먼저 온 트랜잭션의 수행이 완료되어야 다음 트랜잭션이 작업을 시작할 수 있다.
4) Durability (지속성)
레스토랑에서 식사를 하고서 카드 결제를 하고 기분 좋게 집에 왔다.
그런데 그 레스토랑에서 전화가 왔다. 결제를 안 했단다. 뭐라고?
이야기를 들어보니 레스토랑 카운터의 컴퓨터가 잠시 먹통이 되어 껐다 켰는데, 주문 내역을 보니 나의 주문 내역은 있는데 결제 내역은 없다고 한다.
그래서 나의 카드 결제 내역을 보내서 재확인을 요청했고, 레스토랑이 카드사에 확인을 하니 기록이 있었다.그런데 갑자기 번개가 쳐서 카드사 서버가 다운이 되었다가 몇 분 후에 복구되었다고 한다.
혹시나 레스토랑처럼 내 결제 내역이 없다고 하면 어쩌지? 전화를 해서 물어보니 다행히도 기록이 잘 남아 있었다. 그렇게 해결이 되었다.
트랜잭션의 지속성이란 각각의 트랜잭션이 수행되면 그 결과가 '영구적'으로 저장이 되는 것을 뜻한다.
여기서 '영구적'이라는 말은 데이터베이스가 있는 컴퓨터가 갑자기 꺼지든, 렉이 걸리든 상관 없이 트랜잭션의 수행 기록이 남아 있음을 의미한다.
위 이야기에서 레스토랑은 트랜잭션의 지속성이 없고, 카드사는 지속성이 있다고 볼 수 있다.
트랜잭션의 상태
트랜잭션의 작동 상태는 5가지로 구분된다.
- 활동(Active) : 트랜잭션이 시작되어 실행 중인 상태
- 부분 완료(Partially Commited) : 트랜잭션의 모든 과정이 완료되었고, COMMIT이 되기 직전인 상태
- 완료(Commited) : 트랜잭션의 모든 과정이 완료되었고, COMMIT이 된 상태
- 실패(Failed) : 트랜잭션 과정에 (단 1이라도) 오류가 발생하여 중지된 상태
- 철회(Aborted) : 트랜잭션 오류로 인해 비정상 종료되어 ROLLBACK된 상태
트랜잭션의 연산
트랜잭션의 연산으로 COMMIT, ROLLBACK, SAVEPOINT 등이 있다. 추가로 COMMIT의 자동 수행 여부를 결정하는 AUTOCOMMIT 기능이 있다.
- COMMIT : 트랜잭션이 제대로 수행된 후 그 결과를 확정하여 데이터베이스에 반영한다는 명령
- ROLLBACK : 트랜잭션 수행 도중 오류가 발생하여 트랜잭션이 시작되기 전 상태로 되돌리는 명령
- SAVEPOINT
- 트랜잭션 과정을 여러 개로 나누어 주기 위한 명령
- 이를 통해 트랜잭션 과정의 일부만 ROLLBACK 가능
- AUTOCOMMIT : 각각의 SQL문을 하나의 트랜잭션으로 취급하여 매 SQL문마다 COMMIT이 되게 함
- SET AUTOCOMMIT = OFF
- 하나의 트랜잭션 단위로 COMMIT
- SET AUTOCOMMIT = ON
- 각각의 SQL문을 트랜잭션으로 취급하여 매 SQL문마다 COMMIT
- SET AUTOCOMMIT = OFF
(아래 예시는 MariaDB로 실행하였습니다.)
(불필요한 결과 출력은 생략하였습니다.)
1) COMMIT
MariaDB [nation]> SET autocommit = ON;
MariaDB [nation]> SELECT * FROM guests; -- 1
+----------+-------+
| guest_id | name |
+----------+-------+
| 1 | John |
| 2 | Jane |
| 3 | Jean |
| 4 | Storm |
| 5 | Beast |
+----------+-------+
MariaDB [nation]> START TRANSACTION; -- autocommit ON 상태에서 이걸 쓰면 COMMIT이나 ROLLBACK을 하기 전까지는 쿼리 반영이 되지 않음
MariaDB [nation]> INSERT INTO guests VALUE (10, 'a');
MariaDB [nation]> SELECT * FROM guests; -- 2
+----------+-------+
| guest_id | name |
+----------+-------+
| 1 | John |
| 2 | Jane |
| 3 | Jean |
| 4 | Storm |
| 5 | Beast |
| 10 | a |
+----------+-------+
MariaDB [nation]> INSERT INTO guests VALUE (11, 'b');
MariaDB [nation]> UPDATE guests SET name='Name_Changed' WHERE name='John';
MariaDB [nation]> SELECT * FROM guests; -- 3
+----------+--------------+
| guest_id | name |
+----------+--------------+
| 1 | Name_Changed |
| 2 | Jane |
| 3 | Jean |
| 4 | Storm |
| 5 | Beast |
| 10 | a |
| 11 | b |
+----------+--------------+
MariaDB [nation]> DELETE FROM guests WHERE guest_id = 11;
MariaDB [nation]> SELECT * FROM guests; -- 4
+----------+--------------+
| guest_id | name |
+----------+--------------+
| 1 | Name_Changed |
| 2 | Jane |
| 3 | Jean |
| 4 | Storm |
| 5 | Beast |
| 10 | a |
+----------+--------------+
MariaDB [nation]> COMMIT;
MariaDB [nation]> SELECT * FROM guests; -- 5
+----------+--------------+
| guest_id | name |
+----------+--------------+
| 1 | Name_Changed |
| 2 | Jane |
| 3 | Jean |
| 4 | Storm |
| 5 | Beast |
| 10 | a |
+----------+--------------+
START TRANSACTION으로 트랜잭션을 시작하여 COMMIT으로 끝을 내어 트랜잭션의 결과가 데이터베이스에 적용이 되었다.
(※ 참고 : autocommit을 OFF로 설정하면 START TRANSACTION을 쓰지 않아도 각 쿼리문들의 실행 결과가 COMMIT나 ROLLBACK을 하기 전까지 반영되지 않는다.)
2) ROLLBACK
MariaDB [nation]> SET autocommit = ON;
MariaDB [nation]> SELECT * FROM guests; -- 1
+----------+-------+
| guest_id | name |
+----------+-------+
| 1 | John |
| 2 | Jane |
| 3 | Jean |
| 4 | Storm |
| 5 | Beast |
+----------+-------+
MariaDB [nation]> START TRANSACTION;
MariaDB [nation]> INSERT INTO guests VALUE (10, 'a');
MariaDB [nation]> SELECT * FROM guests; -- 2
+----------+-------+
| guest_id | name |
+----------+-------+
| 1 | John |
| 2 | Jane |
| 3 | Jean |
| 4 | Storm |
| 5 | Beast |
| 10 | a |
+----------+-------+
MariaDB [nation]> INSERT INTO guests VALUE (11, 'b');
MariaDB [nation]> UPDATE guests SET name='Name_Changed' WHERE name='John';
MariaDB [nation]> SELECT * FROM guests; -- 3
+----------+--------------+
| guest_id | name |
+----------+--------------+
| 1 | Name_Changed |
| 2 | Jane |
| 3 | Jean |
| 4 | Storm |
| 5 | Beast |
| 10 | a |
| 11 | b |
+----------+--------------+
MariaDB [nation]> DELETE FROM guests WHERE guest_id = 11;
MariaDB [nation]> SELECT * FROM guests; -- 4
+----------+--------------+
| guest_id | name |
+----------+--------------+
| 1 | Name_Changed |
| 2 | Jane |
| 3 | Jean |
| 4 | Storm |
| 5 | Beast |
| 10 | a |
+----------+--------------+
MariaDB [nation]> ROLLBACK;
MariaDB [nation]> SELECT * FROM guests; -- 5
+----------+-------+
| guest_id | name |
+----------+-------+
| 1 | John |
| 2 | Jane |
| 3 | Jean |
| 4 | Storm |
| 5 | Beast |
+----------+-------+
START TRANSACTION으로 트랜잭션을 시작하여 ROLLBACK으로 끝을 냈다. 그 결과 그 사이에 있던 모든 쿼리들에 대한 실행 결과가 트랜잭션의 시작 전으로 되돌아갔다.
3) SAVEPOINT
MariaDB [nation]> SELECT * FROM guests; -- 1
+----------+-------+
| guest_id | name |
+----------+-------+
| 1 | John |
| 2 | Jane |
| 3 | Jean |
| 4 | Storm |
| 5 | Beast |
+----------+-------+
MariaDB [nation]> START TRANSACTION;
MariaDB [nation]> INSERT INTO guests VALUE (10, 'a');
MariaDB [nation]> SELECT * FROM guests; -- 2
+----------+-------+
| guest_id | name |
+----------+-------+
| 1 | John |
| 2 | Jane |
| 3 | Jean |
| 4 | Storm |
| 5 | Beast |
| 10 | a |
+----------+-------+
MariaDB [nation]> SAVEPOINT two; -- 이 지점 이전으로 상태를 되돌릴 예정
MariaDB [nation]> INSERT INTO guests VALUE (11, 'b');
MariaDB [nation]> UPDATE guests SET name='Name_Changed' WHERE name='John';
MariaDB [nation]> SELECT * FROM guests; -- 3
+----------+--------------+
| guest_id | name |
+----------+--------------+
| 1 | Name_Changed |
| 2 | Jane |
| 3 | Jean |
| 4 | Storm |
| 5 | Beast |
| 10 | a |
| 11 | b |
+----------+--------------+
MariaDB [nation]> DELETE FROM guests WHERE guest_id = 11;
MariaDB [nation]> SELECT * FROM guests; -- 4
+----------+--------------+
| guest_id | name |
+----------+--------------+
| 1 | Name_Changed |
| 2 | Jane |
| 3 | Jean |
| 4 | Storm |
| 5 | Beast |
| 10 | a |
+----------+--------------+
MariaDB [nation]> ROLLBACK TO two;
MariaDB [nation]> SELECT * FROM guests; -- 5
+----------+-------+
| guest_id | name |
+----------+-------+
| 1 | John |
| 2 | Jane |
| 3 | Jean |
| 4 | Storm |
| 5 | Beast |
| 10 | a |
+----------+-------+
SAVEPOINT two를 설정 후, ROLLBACK을 해당 시점으로 지정하여 실행하였다. 그 결과 SAVEPOINT two 이전에 수행되었던 쿼리의 결과는 남고, 그 이후의 쿼리 결과들은 사라진 것을 확인할 수 있다.
4) AUTOCOMMIT
SET autocommit = ON;
MariaDB [nation]> SELECT * FROM guests; -- 1
+----------+-------+
| guest_id | name |
+----------+-------+
| 1 | John |
| 2 | Jane |
| 3 | Jean |
| 4 | Storm |
| 5 | Beast |
+----------+-------+
MariaDB [nation]> INSERT INTO guests VALUE (10, 'a');
MariaDB [nation]> INSERT INTO guests VALUE (11, 'b');
MariaDB [nation]> UPDATE guests SET name='Name_Changed' WHERE name='John';
MariaDB [nation]> SELECT * FROM guests; -- 2
+----------+--------------+
| guest_id | name |
+----------+--------------+
| 1 | Name_Changed |
| 2 | Jane |
| 3 | Jean |
| 4 | Storm |
| 5 | Beast |
| 10 | a |
| 11 | b |
+----------+--------------+
MariaDB [nation]> ROLLBACK;
MariaDB [nation]> SELECT * FROM guests; -- 3
+----------+--------------+
| guest_id | name |
+----------+--------------+
| 1 | Name_Changed |
| 2 | Jane |
| 3 | Jean |
| 4 | Storm |
| 5 | Beast |
| 10 | a |
| 11 | b |
+----------+--------------+
<참고 자료>
- 데이터베이스 트랜잭션 - 위키백과
- transaction - Cambridge English Dictionary
- transaction - engoo
- 트랜잭션 - 해시넷
- [DB기초] 트랜잭션이란 무엇인가? - 코딩팩토리
- [DataBase] DB를 지탱하는 트랜잭션 - by Nathan
- [데이터베이스] Transaction, 트랜잭션이란? - Wonit
- [DB]트랜잭션(Transaction)이란?/트랜잭션의 개념,특징, 연산과정/savepoint - 튜나 개발일기
- [Docker/MySQL] 트랜잭션(Transaction) - yunaaaas
- [JPA] jdbc에서의 Transaction - 하루사리
- 트랜잭션(Azure Synapse Analytics) -Microsoft Docs
- Use Commit and Rollback to Manage MySQL Transactions in Python
- ACID
'Database > Knowledge' 카테고리의 다른 글
[DB] 데이터베이스 언어 (Database Language) (0) | 2021.11.25 |
---|---|
[DB] 데이터베이스 스키마(Database Schema) (feat. 데이터베이스 사상(Database Mapping)) (0) | 2021.11.24 |
[DB] 데이터베이스 정규화(Database Normalization) (0) | 2021.11.21 |
[DB] 데이터베이스 관계(Database Relationships) (0) | 2021.11.18 |