CS/DB

[Database] 데이터베이스 트랜잭션(Transaction) & ACID

Uykm 2023. 11. 4. 12:09

▼ 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) 사용 패턴

 

  1. 트랜잭션(Transaction)을 시작한다.

  2. 데이터를 읽거나 쓰는 등의 SQL문들을 포함해서 로직을 수행한다.

  3. 일련의 과정들이 문제없이 수행됐다면 트랜잭션을 COMMIT 한다.

  4. 그러나 중간에 문제가 발생했다면 트랜잭션을 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언제 해야 할지 !

 


 

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이 수행돼야 한다 !
    규칙을 위반했는지의 여부DBMSCOMMIT을 하기 전에 확인해준다.
    ( 단, 애플리케이션(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) 저장(반영)된다.