▼ Why ? What ?
"Database" 강의시간에 배운 트랜잭션(Transaction)과 ACID는 데이터베이스를 다루기 전에 꼭 알고 있어야 하는 개념이기 때문에 따로 더 공부하고 정리해두려고 한다.
▼ DB Transaction
트랜잭션(Transaction) ?
- 트랜잭션 (Transaction)
: 단일한 논리적인 작업 단위 (A single logical unit of work) - 논리적인 이유로 여러 SQL문들을 단일 작업으로 묶어서 나눠질 수 없게 만든 것 !
쉽게 말하면, 데이터베이스 애플리케이션에서 하나의 작업(logical function)을 수행하기 위해 필요한 DB 연산들을 모아놓은 것을 말한다.
➜ Transaction의 SQL문들 중에 일부만 성공해서 DB에 반영되는 일을 발생 X - 뱅킹(Banking) 작업을 예로 들었을 때, A가 B에게 20만원을 이체한다고 해보자.
➜ A와 B의 계좌는 어떻게 변경돼야 할까?
(1) A의 계좌엔 100만원이 있고, B의 계좌엔 200만원이 있다.
(2) A의 계좌에선 "20만원을 출금"해서 80만원이 될 것이고, B의 계좌에선 "20만원이 입금"돼 220만원이 될 것이다.
➜ 이 하나의 작업(transaction)을 SQL로 표현해보자.
UPDATE account SET balance = balance - 200000 WHERE id = 'A';
UPDATE account SET balance = balance + 200000 WHERE id = 'B';
🔻A가 B에게 20만원을 이체하는 하나의 작업(transaction)이 수행되어 은행(Bank) 데이터베이스에 반영되기 위해선 ?
➜ 당연하게도 이 작업에 필요한 두 개의 DB 연산, 즉 SQL문들이 모두 정삭적으로 처리되어야만 이체라는 작업이 정상적으로 수행될 수 있는 것이다 !
트랜잭션(Transaction) 활용 - MySQL
- 앞서 예로 들었던 이체 예제를 MySQL에서 트랜잭션을 사용해서 구현해보자 !
- A가 B에게 20만원을 이체하는 작업.
- START TRANSACTION; ➜ 트랜잭션 시작.
- COMMIT; ➜ 지금까지 작업한 내용을 DB에 영구적으로(permanently) 저장 + 트랜잭션 종료.
select * from account; -- account 테이블 조회
+----+---------+
| id | balance |
+----+---------+
| A | 1000000 |
| B | 2000000 |
+----+---------+
START TRANSACTION; -- 트랜잭션을 시작
UPDATE account SET balance = balance - 200000 WHERE id = 'A';
UPDATE account SET balance = balance + 200000 WHERE id = 'B';
COMMIT -- 지금까지 작업한 내용을 DB에 영구적으로(permanently) 저장하고, 트랜잭션을 종료
select * from account;
+----+---------+
| id | balance |
+----+---------+
| A | 800000 |
| B | 2200000 |
+----+---------+
- A가 B에게 30만원 추가로 이체하는 작업.
- ROLLBACK; ➜ 지금까지의 작업들을 모두 취소하고 트랜잭션 이전 상태로 롤백 + 트랜잭션 종료.
START TRANSACTION;
UPDATE account SET balance = balance - 300000 WHERE id = 'A';
select * from account;
+----+---------+
| id | balance |
+----+---------+
| A | 500000 |
| B | 2200000 |
+----+---------+
ROLLBACK;
select * from account;
+----+---------+
| id | balance |
+----+---------+
| A | 800000 |
| B | 2200000 |
+----+---------+
AUTOCOMMIT
- 각각의 SQL문을 자동으로 트랜잭션(transaction) 처리.
➜ SQL문이 성공적으로 실행되면 자동으로 COMMIT. - 실행 중에 문제가 있었다면 자동으로 ROLLBACK.
- MySQL에선 Default로 'AUTOCOMMIT' 이 enabled 되어 있다 !
( 다른 DBMS에서도 대부분 같은 기능을 제공한다. )
SELECT @@AUTOCOMMIT;
+--------------+
| @@AUTOCOMMIT |
+--------------+
| 1 |
+--------------+
- AUTOCOMMIT 사용 예제
➜ insert문을 실행하면 자동으로 COMMIT이 되면서 account 테이블에 ('C', 1000000) 데이터가 영구적으로 저장된다 !
insert into account values ('W', 1000000);
select * from account;
+----+---------+
| id | balance |
+----+---------+
| A | 800000 |
| B | 2200000 |
| C | 1000000 |
+----+---------+
- 만약 AUTOCOMMIT이 비활성화 상태일 때 ROLLBACK을 수행한다면 ?
➜ COMMIT이 되지 않은 상태이기 때문에 트랜잭션 이전 상태로 복원 가능 !
SET autocommit=0; -- AUTOCOMMIT 비활성화.
DELETE FROM account WHERE balance <= 1000000;
select * from account;
+----+---------+
| id | balance |
+----+---------+
| A | 800000 |
+----+---------+
ROLLBACK;
+----+---------+
| id | balance |
+----+---------+
| A | 800000 |
| B | 2200000 |
| C | 1000000 |
+----+---------+
- 사실, AUTOCOMMIT은 'START TRANSACTION;' 실행과 동시에 off 된다 !
➜ COMMIT / ROLLBACK 과 함께 트랜잭션(transaction)이 종료되면 다시 enabled 상태로 돌아간다.
트랜잭션(Transaction) 사용 패턴
- 트랜잭션(Transaction)을 시작한다.
- 데이터를 읽거나 쓰는 등의 SQL문들을 포함해서 로직을 수행한다.
- 일련의 과정들이 문제없이 수행됐다면 트랜잭션을 COMMIT 한다.
- 그러나 중간에 문제가 발생했다면 트랜잭션을 ROLLBACK 한다.
트랜잭션(Transaction) 사용 예제 - Java & Spring
Java
- 트랜잭션과 관련된 부분만 명시적으로 나타낸 코드이다.
public void transfer(String fromId, String told, int amount) {
try {
Connection connection = ...; // get DB connection ...
connection.setAutoCommit(false); // "START TRANSACTION"
// 로직 수행 구현부: 사실상 이체 같은 작업과 관련된 코드는 이 부분 뿐이다 !
... // update at fromId
... // update at told
connection.commit();
} catch (Exception e) {
...
connection.rollback(); // 에러가 발생한 경우
...
} finally { // COMMIT이 됐던, ROLLBACK이 됐던 AUTOCOMMIT은 다시 Enabled 상태로 바꿔줘야 한다.
connection.setAutoCommit(true);
}
}
Spring
- 로직 수행과 관련된 부분만 남기고, 트랜잭션과 관련된 나머지 코드들은 숨기고 싶다.
➜ @Transactional 애너테이션 사용 !
@Transactional
public void transfer(String fromId, String told, int amount) {
// 로직과 관련된 부분만 남긴다 !
... // update at fromId
... // update at told
}
▼ ACID
ACID ?
- 데이터베이스 내에서 생겨나는 트랜잭션(transaction)들의 안정성을 보장하기 위한 성질이자 규칙이다 !
Atomicity
- ALL or NOTHING
➜ 모두 성공하거나 모두 실패하거나 ! - 트랜잭션(Transaction)은 "논리적으로 쪼개질 수 없는 작업 단위" 이기 때문에 내부의 SQL문들이 모두 성공해야 한다는 것을 의미하는 트랜잭션의 속성이다 !
- 앞서 예로 들었던 A가 B에게 20만원을 이체하는 과정에서 2개의 SQL문이 필요했는데, 둘 중에 하나의 SQL 문이라도 정삭적으로 수행되지 않는다면 트랜잭션(transaction)이 수행되지 않았던 것을 생각해보자 !
➜ 중간의 SQL문이 실패하면 지금까지의 작업을 모두 취소하여 아무런 작업도 수행되지 않았던 상태로 ROLLBACK 한다. - "Atomicity" 를 만족하기 위한 DBMS의 역할과 개발자의 역할은 ?
- DBMS
➜ COMMIT 실행 시에 DB에 영구적으로 저장하는 것.
➜ ROLLBACK 실행 시에 이전 상태로 되돌리는 것. - 개발자 ➜ COMMIT 혹은 ROLLBACK을 언제 해야 할지 !
- DBMS
Consistency
- DB의 "일관성(consistency)" 를 유지시키는 것 !
➜ 이번에도 이체라는 작업을 예로 들면,
아래의 UPDATE문은 미리 테이블에 걸어둔 제약(Constraints)에 따라 일관성을 깨뜨리는 "inconsistent" 한 SQL문이라 판단되어 ROLLBACK이 수행될 것이다.
CREATE TABLE account (
...,
balance INT,
check (balance >= 0) -- 계좌의 잔액이 마이너스가 되지 않도록 제약(Constraints)를 걸어줬다.
)
UPDATE account SET balance = balance - 1000000 WHERE id = 'A'; -- A의 잔고: -20만원
- 트랜잭션(Transaction)은 DB 상태를 "consistent" 상태에서 또 다른 "consistent" 상태로 바꿔줘야 한다.
- 제약(Constraints)이나 트리거(Trigger) 등을 통해 DB에 정의된 규칙(Rule)을 트랜잭션이 위반했다면 앞서 말한 예제처럼 ROLLBACK이 수행돼야 한다 !
➜ 규칙을 위반했는지의 여부는 DBMS가 COMMIT을 하기 전에 확인해준다.
( 단, 애플리케이션(Application) 관점에선 트랜잭션이 "consistent" 하게 동작하는지는 개발자가 체크해야 할 부분이다 ! )
Isolation
- 여러 트랜잭션(Transaction)이 동시에 실행될 경우에도 각각의 트랜잭션이 혼자 실행되는 것처럼 동작하게 만든다 !
➜ A가 B에게 20만원을 이체(Transaction 1)하는 도중에 B도 A에게 30만원을 이체(Transaction 2)하는 작업이 수행했다고 가정하자.
이때 Isolation이 제대로 지켜지지 않았다?
➜ "Transaction 1" 은 "Transaction 2" 가 수행되기 이전의 테이블 상태로 트랜잭션을 시작했다. 그런데 "Transaction 2" 이 COMMIT을 수행하여 먼저 마치게 된다면, "Transaction 1" 이 COMMIT으로 종료되었을 땐 "Transaction 2" 의 결과를 반영하지 못한 "Transactoin 2" 가 수행되기 이전의 상태에 "Transaction 1" 의 결과를 반영하여 잘못된 결과가 DB에 저장되게 된다. - DBMS는 여러 종류의 "Isolation Level" 을 제공한다.
➜ Isolation이라는 속성에 따르는 정도, 즉 "isolation level" 이 높으면 높을수록 트랜잭션들 사이에 영향력은 줄어들어 안정성이 높아지지만, 여러 트랜잭션들이 동시에 실행될 수 있는 동시성이 떨어지기 때문에 DB Server의 성능(performance)가 떨어지게 된다 !
쉽게 말하면, 경우에 따라 Isolation을 얼마나 엄격하게 따를 것인지에 따라 안정적이지만 성능이 떨어질 수도, 성능은 올라갔지만 정상적인 결과를 출력하지 못할 수도 있게 된다.
➜ 이러한 장단점들 때문에 각 상황에 맞는 정도를 선택하기 위해 여러 종류의 "isolation level" 을 제공해주는 것 ! - "Isolation level" 중에 어떤 Level로 트랜잭션을 동작시킬지 설정해줘야 한다 !
( Default로 설정된 level이 있긴 하다. ) - "Concurrency-control" 의 주된 목적이 "Isolation" 인 것이다 !
Durability
- COMMIT이 수행된 트랜잭션(Transaction)은 DB에 영구적(permanently)으로 저장된다는 것을 의미한다 !
➜ DB 시스템에 'power failure' 이나 'operating system crash' 와 같은 문제가 발생하거나 트랜잭션 자체에 문제(transaction failure)가 발생해도 COMMIT 된 트랜잭션은 DB에 여전히 남아있다.
( "영구적으로 저장한다" 라는 말은 일반적으로 "비휘발성 메모리(HDD, SDD, ...)에 저장한다" 는 것을 의미한다. ) - 기본적으로 트랜잭션의 "Durability" 는 DBMS가 보장해준다 !
➜ 개발자는 이 부분도 수정해줄 수는 있지만 굳이 그럴 필요 X
"ACID" 를 숙지하고 있어야 하는 이유
- 트랜잭션(Transaction)을 올바르게 정의하기 위해선, 구현하려는 기능은 물론, ACID 속성을 이해해야 할 필요가 있다 !
- DBMS가 모든 것을 알아서 해주는 것은 아니라는 사실을 기억하자 !
▼ Transaction Management
트랜잭션(Transaction) 수행 과정
- DB에서 트랜잭션이 수행되는 과정은 다음과 같다.
Active
- 모든 트랜잭션(transaction)의 초기 상태(Initial state).
Paritially Commited
- 트랜잭션(Transaction)이 마지막 연산(final operaiton)을 수행할 때의 상태.
Failed
- 어떤 동작이 정상적으로 수행되지 못한 것이 DB의 복원 시스템(recovery system)에 의해 체크됐을 때의 상태이며, 이 경우엔 더이상 해당 트랜잭션(failed trasaction)은 더이상 실행 X !
Aborted
- 어떠한 트랜잭션(transaction)이 "Failed" 상태가 됐다면, 해당 트랜잭션이 수행되기 이전의 상태(original state)로 ROLLBACK 해준다.
➜ 이후에 DB 복원 모듈(recovery module)이 다음과 같은 2개의 연산 중 하나를 선택하여 수행하게 된다.
- Re-start the transaction
- Kill the transaction
Committed
- 하나의 트랜잭션의 모든 연산(operations)이 성공적으로 수행됐다면, 이를 "Committed" 라 말한다 !
➜ 해당 트랜잭션이 수행된 결과는 DB에 영구적으로(parmanently) 저장(반영)된다.