트랜잭션 (Transactions)

데이터베이스의 상태를 변화시키기 위해 수행하는 작업의 단위
트랜잭션은 데이터베이스의 무결성을 보장하고 일관성 있는 상태를 유지하는 데 중요한 역할을 한다.

데이터베이스의 상태를 변화시키는 하나의 논리적 작업 단위를 구성하는 연산들의 집합.
이는 한 번에 모두 수행되어야 할 일련의 데이터베이스 연산들을 의미한다.

트랜잭션의 역할:

  1. 데이터 무결성 보장: 여러 작업이 하나의 단위로 처리되어 부분적인 데이터 변경을 방지합니다.
  2. 동시성 제어: 여러 사용자가 동시에 데이터에 접근할 때 데이터의 일관성을 유지합니다.
  3. 오류 복구: 트랜잭션 실행 중 오류가 발생하면 이전 상태로 롤백하여 데이터의 안정성을 보장합니다.
  4. 복잡한 비즈니스 로직 처리: 여러 단계의 작업을 하나의 논리적 단위로 처리할 수 있게 합니다.

트랜잭션의 특성 (ACID):

  1. 원자성 (Atomicity)

    • 트랜잭션의 모든 연산이 완전히 수행되거나 전혀 수행되지 않아야 합니다
    • 중간 단계까지 실행되다가 실패하면 처음부터 다시 시작해야 합니다
    • 예: 계좌이체 시 출금과 입금이 모두 성공하거나, 둘 다 실패해야 합니다
  2. 일관성 (Consistency)

    • 트랜잭션 실행 전과 후의 데이터베이스는 일관된 상태를 유지해야 합니다
    • 데이터베이스의 제약조건을 위반하지 않아야 합니다
    • 예: 계좌 잔고는 항상 0 원 이상이어야 한다는 규칙이 있다면, 이를 위반할 수 없습니다
  3. 독립성 (Isolation)

    • 동시에 실행되는 트랜잭션들이 서로 영향을 미치지 않아야 합니다
    • 한 트랜잭션의 중간 결과가 다른 트랜잭션에게 보이지 않아야 합니다
    • 예: A 가 계좌이체 중일 때 B 가 같은 계좌를 조회해도 이체 완료 전까지는 원래 금액이 보입니다
  4. 지속성 (Durability)

    • 성공적으로 완료된 트랜잭션의 결과는 영구적으로 반영되어야 합니다
    • 시스템 장애가 발생하더라도 데이터는 보존되어야 합니다
    • 예: 계좌이체 완료 후 정전이 되어도 이체 내역은 보존됩니다

동작 과정:

  1. 트랜잭션 시작: START TRANSACTION 명령으로 시작합니다.
  2. 작업 수행: 데이터베이스 작업을 실행합니다.
  3. 커밋 또는 롤백:
    • 모든 작업이 성공적으로 완료되면 COMMIT 을 실행하여 변경사항을 영구적으로 반영합니다.
    • 오류 발생 시 ROLLBACK 을 실행하여 트랜잭션 시작 전 상태로 되돌립니다.

트랜잭션의 기본 구조:

1
2
3
4
5
6
7
8
BEGIN TRANSACTION;  -- 트랜잭션 시작
    -- 데이터베이스 작업들
    작업1;
    작업2;
    작업3;
COMMIT;            -- 트랜잭션 완료
-- 또는
ROLLBACK;          -- 트랜잭션 취소

트랜잭션의 상태 트랜잭션:

제어 명령어:

  1. BEGIN TRANSACTION: 트랜잭션 시작
  2. COMMIT: 트랜잭션 완료 및 변경사항 영구 저장
  3. ROLLBACK: 트랜잭션 취소 및 변경사항 폐기
  4. SAVEPOINT: 트랜잭션 내에 중간 저장점 생성

트랜잭션의 동시성 제어:
여러 트랜잭션이 동시에 실행될 때 발생할 수 있는 문제를 해결하기 위해 다음과 같은 기법을 사용한다.

  1. 잠금 (Locking): 공유 잠금 (Shared Lock) 과 배타적 잠금 (Exclusive Lock) 을 사용하여 동시 접근을 제어합니다.
  2. 타임스탬프 기반 프로토콜: 각 트랜잭션에 고유한 타임스탬프를 부여하여 충돌을 해결합니다.
  3. 다중 버전 동시성 제어 (MVCC): 데이터의 여러 버전을 유지하여 읽기 작업의 동시성을 향상시킵니다.

트랜잭션 격리 수준:
SQL 표준은 네 가지 트랜잭션 격리 수준을 정의한다.

  1. READ UNCOMMITTED: 가장 낮은 격리 수준으로, 더티 리드가 발생할 수 있습니다.
  2. READ COMMITTED: 커밋된 데이터만 읽을 수 있지만, 반복 불가능한 읽기가 발생할 수 있습니다.
  3. REPEATABLE READ: 트랜잭션 내에서 동일한 쿼리는 항상 같은 결과를 반환하지만, 팬텀 읽기가 발생할 수 있습니다.
  4. SERIALIZABLE: 가장 높은 격리 수준으로, 완전한 일관성을 보장하지만 성능 저하가 발생할 수 있습니다.

장점:

단점:

트랜잭션의 주요 특성과 고려사항

  1. 트랜잭션 격리 수준 (Transaction Isolation Level)

    1
    2
    3
    4
    5
    6
    7
    
    -- 격리 수준 설정 예시
    SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
    
    BEGIN TRANSACTION;
        SELECT * FROM products WHERE id = 1;
        -- 이 시점에서 다른 트랜잭션의 커밋된 변경사항만 볼 수 있음
    COMMIT;
    
  2. 교착상태 (Deadlock) 처리

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    -- 교착상태 감지 및 처리
    BEGIN TRANSACTION;
        SET DEADLOCK_PRIORITY LOW;  -- 교착상태 발생 시 이 트랜잭션이 희생됨
    
        UPDATE table1 SET col1 = value1 WHERE id = 1;
        UPDATE table2 SET col2 = value2 WHERE id = 1;
    
        -- 교착상태 발생 시 자동으로 롤백됨
    COMMIT;
    
  3. 중첩 트랜잭션 (Nested Transactions)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    BEGIN TRANSACTION outer_trans;
        -- 외부 트랜잭션 작업
    
        BEGIN TRANSACTION inner_trans;
            -- 내부 트랜잭션 작업
        COMMIT TRANSACTION inner_trans;
    
        -- 외부 트랜잭션 계속
    COMMIT TRANSACTION outer_trans;
    
  4. 세이브포인트 (Savepoint) 활용

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    
    BEGIN TRANSACTION;
    
        INSERT INTO orders (product_id, quantity) VALUES (1, 1);
        SAVEPOINT after_order;  -- 세이브포인트 생성
    
        UPDATE inventory SET stock = stock - 1;
        -- 재고 업데이트 실패 시
        ROLLBACK TO after_order;  -- 주문 정보는 유지한 채 재고만 롤백
    
    COMMIT;
    

트랜잭션 설계 시 고려해야 할 실무적 팁들

  1. 트랜잭션 범위 최소화
    트랜잭션은 가능한 한 작게 유지하여 다른 트랜잭션과의 충돌 가능성을 줄여야 한다.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    
    -- 좋지 않은 예
    BEGIN TRANSACTION;
        -- 오래 걸리는 데이터 처리
        WHILE (SELECT COUNT(*) FROM huge_table) > 0
        BEGIN
            -- 처리 로직
        END
    COMMIT;
    
    -- 좋은 예
    BEGIN TRANSACTION;
        -- 작은 단위로 나누어 처리
        DECLARE @batch_size INT = 1000;
        DELETE TOP (@batch_size) FROM huge_table;
    COMMIT;
    
  2. 오류 처리
    모든 트랜잭션은 적절한 오류 처리 로직을 포함해야 한다.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    
    BEGIN TRY
        BEGIN TRANSACTION;
            -- 트랜잭션 로직
        COMMIT;
    END TRY
    BEGIN CATCH
        IF @@TRANCOUNT > 0
            ROLLBACK;
    
        -- 오류 로깅
        INSERT INTO error_logs (error_message, error_time)
        VALUES (ERROR_MESSAGE(), GETDATE());
    
        -- 오류 재발생
        THROW;
    END CATCH
    
  3. 성능 모니터링
    트랜잭션의 성능을 모니터링하고 최적화하는 것이 중요.

    1
    2
    3
    4
    5
    6
    7
    8
    
    -- 트랜잭션 성능 모니터링 쿼리
    SELECT 
        session_id,
        transaction_id,
        transaction_begin_time,
        DATEDIFF(SECOND, transaction_begin_time, GETDATE()) as duration_seconds
    FROM sys.dm_tran_active_transactions
    WHERE transaction_type = 1;  -- 사용자 트랜잭션만 조회
    

예제

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
-- 온라인 쇼핑몰의 주문 처리 트랜잭션
BEGIN TRANSACTION;

SAVEPOINT order_start;  -- 중간 저장점 생성
TRY {
    -- 1. 재고 확인 및 감소
    UPDATE products
    SET stock = stock - @ordered_quantity
    WHERE product_id = @product_id
    AND stock >= @ordered_quantity;
    
    IF @@ROWCOUNT = 0 THROW 50001, '재고 부족', 1;
    
    -- 2. 주문 정보 저장
    INSERT INTO orders (user_id, product_id, quantity, total_amount)
    VALUES (@user_id, @product_id, @ordered_quantity, @total_amount);
    
    -- 3. 사용자 포인트 차감
    UPDATE users
    SET points = points - @used_points
    WHERE user_id = @user_id
    AND points >= @used_points;
    
    IF @@ROWCOUNT = 0 THROW 50002, '포인트 부족', 1;
    
    -- 4. 결제 처리
    INSERT INTO payments (order_id, payment_method, amount)
    VALUES (@order_id, @payment_method, @total_amount);
    
    COMMIT TRANSACTION;
}
CATCH {
    ROLLBACK TRANSACTION TO order_start;
    -- 오류 로깅 및 처리
    INSERT INTO error_logs (error_message, error_time)
    VALUES (ERROR_MESSAGE(), GETDATE());
}

데이터베이스에서 트랜잭션은 데이터베이스의 상태를 변화시키는 논리적 작업 단위이다.

트랜잭션은 데이터베이스의 무결성과 일관성을 보장하는 핵심 개념으로 ACID 속성을 통해 데이터의 신뢰성을 보장하며, 다양한 격리 수준을 통해 동시성 제어가 가능하다. 현대 데이터베이스 시스템은 대부분 트랜잭션을 지원하며, 각 시스템마다 구현 방식과 기능에 차이가 있다.

트랜잭션을 올바르게 이해하고 적용하면 데이터 일관성을 유지하면서도 시스템의 성능과 확장성을 최적화할 수 있다. 특히 금융, 전자상거래, 재고 관리와 같은 중요한 비즈니스 시스템에서는 트랜잭션의 올바른 구현이 필수적이다.

트랜잭션의 기본 개념

트랜잭션은 데이터베이스의 무결성을 보장하기 위한 핵심 메커니즘이다.

은행 시스템을 예로 들어보면 계좌에서 1 만원을 인출하는 과정은 다음과 같은 단계로 구성된다:

  1. 계좌 잔액 확인
  2. 잔액에서 1 만원 차감
  3. 새로운 잔액 저장
  4. 현금 지급
    이 모든 단계가 하나의 트랜잭션을 구성하며, 모든 단계가 성공적으로 완료되거나 아무것도 실행되지 않아야 한다.
    만약 3 단계에서 시스템이 실패한다면, 계좌에서 돈은 차감되었지만 실제로 돈을 받지 못하는 상황이 발생할 수 있다. 트랜잭션은 이러한 불일치 상황을 방지한다.

트랜잭션의 ACID 속성

데이터베이스 트랜잭션은 다음과 같은 ACID 속성을 갖는다:

  1. 원자성 (Atomicity)
    트랜잭션의 모든 연산은 완전히 수행되거나 전혀 수행되지 않아야 한다. 중간 상태는 존재하지 않는다.

    1
    2
    3
    4
    
    BEGIN TRANSACTION;
        UPDATE accounts SET balance = balance - 10000 WHERE account_id = 'A';
        UPDATE accounts SET balance = balance + 10000 WHERE account_id = 'B';
    COMMIT;
    

    위 트랜잭션에서 첫 번째 UPDATE 는 성공했지만 두 번째 UPDATE 가 실패한다면, 첫 번째 UPDATE 도 취소되어야 한다.

  2. 일관성 (Consistency)
    트랜잭션이 완료된 후에도 데이터베이스는 일관된 상태를 유지해야 한다. 모든 무결성 제약조건이 만족되어야 한다. 예를 들어, 계좌 잔액이 항상 0 이상이어야 한다는 제약이 있다면, 트랜잭션 실행 후에도 이 조건은 유지되어야 한다.

  3. 고립성 (Isolation)
    여러 트랜잭션이 동시에 실행될 때, 각 트랜잭션은 다른 트랜잭션의 연산에 영향을 받지 않고 독립적으로 실행되어야 한다.

  4. 지속성 (Durability)
    트랜잭션이 성공적으로 완료 (커밋) 된 후에는 그 결과가 시스템 장애가 발생하더라도 영구적으로 반영되어야 한다.

트랜잭션 제어 명령어

SQL 에서는 다음과 같은 명령어로 트랜잭션을 제어한다:

  1. BEGIN TRANSACTION (또는 START TRANSACTION)
    트랜잭션의 시작을 표시한다.

    1
    
    BEGIN TRANSACTION;
    
  2. COMMIT
    트랜잭션의 모든 변경사항을 데이터베이스에 영구적으로 저장한다.

    1
    
    COMMIT;
    
  3. ROLLBACK
    트랜잭션의 모든 변경사항을 취소하고 트랜잭션 시작 전 상태로 되돌린다.

    1
    
    ROLLBACK;
    
  4. SAVEPOINT
    트랜잭션 내에서 특정 지점을 표시하여, 나중에 해당 지점으로 롤백할 수 있게 한다.

    1
    2
    3
    
    SAVEPOINT save1;
    -- 일부 연산 실행 후
    ROLLBACK TO save1; -- save1 지점으로 롤백
    

트랜잭션 격리 수준

동시에 여러 트랜잭션이 실행될 때, 트랜잭션 간의 격리 정도를 정의한다.

표준 SQL 은 네 가지 격리 수준을 정의한다:

  1. READ UNCOMMITTED
    가장 낮은 격리 수준으로, 한 트랜잭션이 다른 트랜잭션의 커밋되지 않은 변경사항을 읽을 수 있다. 이로 인해 ’ 더티 리드 (Dirty Read)’ 가 발생할 수 있다.

  2. READ COMMITTED
    각 트랜잭션은 다른 트랜잭션이 커밋한 데이터만 읽을 수 있다. 그러나 ’ 반복 불가능한 읽기 (Non-repeatable Read)’ 가 발생할 수 있다.

  3. REPEATABLE READ
    트랜잭션이 읽은 데이터는 트랜잭션이 완료될 때까지 다른 트랜잭션에 의해 변경될 수 없다. 그러나 ’ 팬텀 읽기 (Phantom Read)’ 가 발생할 수 있다.

  4. SERIALIZABLE
    가장 높은 격리 수준으로, 트랜잭션이 완전히 격리되어 동시성 문제가 발생하지 않는다. 그러나 성능이 저하될 수 있다.

격리 수준 설정 예:

1
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;

트랜잭션 관련 문제

  1. 더티 리드 (Dirty Read)
    한 트랜잭션이 아직 커밋되지 않은 다른 트랜잭션의 데이터를 읽는 경우.

  2. 반복 불가능한 읽기 (Non-repeatable Read)
    트랜잭션 내에서 같은 쿼리를 두 번 실행했을 때, 다른 트랜잭션의 커밋으로 인해 결과가 달라지는 경우.

  3. 팬텀 읽기 (Phantom Read)
    트랜잭션 내에서 같은 쿼리를 두 번 실행했을 때, 다른 트랜잭션의 삽입 또는 삭제로 인해 결과 집합이 달라지는 경우.

다양한 DBMS 의 트랜잭션 구현

MySQL (InnoDB)

InnoDB 스토리지 엔진은 ACID 트랜잭션을 완벽하게 지원한다.

1
2
3
START TRANSACTION;
    -- SQL 명령어들
COMMIT;

기본 격리 수준은 REPEATABLE READ 이다.

PostgreSQL

PostgreSQL 은 모든 격리 수준을 지원하며, 기본 격리 수준은 READ COMMITTED 이다.

1
2
3
BEGIN;
    -- SQL 명령어들
COMMIT;

SQL Server

SQL Server 는 모든 트랜잭션 격리 수준을 지원하며, 추가로 SNAPSHOT 격리 수준도 제공한다.

1
2
3
BEGIN TRANSACTION;
    -- SQL 명령어들
COMMIT TRANSACTION;

Oracle

Oracle 은 READ UNCOMMITTED 를 제외한 모든 격리 수준을 지원합니다. 기본 격리 수준은 READ COMMITTED 이다.

1
2
3
BEGIN;
    -- SQL 명령어들
COMMIT;

트랜잭션의 실제 적용 사례

금융 시스템

계좌 이체는 트랜잭션의 대표적인 사례:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
BEGIN TRANSACTION;
    -- 출금 계좌에서 금액 차감
    UPDATE accounts SET balance = balance - 100000 
    WHERE account_id = 'A';
    
    -- 입금 계좌에 금액 추가
    UPDATE accounts SET balance = balance + 100000 
    WHERE account_id = 'B';
    
    -- 거래 내역 기록
    INSERT INTO transactions(from_account, to_account, amount, date) 
    VALUES('A', 'B', 100000, NOW());
COMMIT;

재고 관리 시스템

제품 주문 처리:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
BEGIN TRANSACTION;
    -- 재고 확인
    SELECT quantity FROM inventory WHERE product_id = 123 FOR UPDATE;
    
    -- 재고 업데이트
    UPDATE inventory SET quantity = quantity - 5 WHERE product_id = 123;
    
    -- 주문 생성
    INSERT INTO orders(customer_id, product_id, quantity) 
    VALUES(456, 123, 5);
COMMIT;

트랜잭션 성능 최적화 팁

  1. 트랜잭션은 가능한 짧게 유지하세요.
    긴 트랜잭션은 잠금 경합과 데이터베이스 리소스를 과도하게 사용할 수 있다.

  2. 불필요한 작업은 트랜잭션 외부로 이동하세요.
    데이터 조회만 필요한 경우 트랜잭션이 필요하지 않을 수 있다.

  3. 적절한 격리 수준을 선택하세요.
    모든 상황에 SERIALIZABLE 이 필요하지는 않다. 애플리케이션 요구사항에 맞는 최소한의 격리 수준을 선택한다.

  4. 데드락을 방지하기 위해 항상 같은 순서로 테이블에 접근하세요.
    여러 테이블에 접근할 때 일관된 순서를 유지하면 데드락 가능성이 줄어든다.

  5. 트랜잭션 로그 관리에 주의하세요.
    대용량 트랜잭션은 로그 파일을 급격히 증가시킬 수 있다.

분산 트랜잭션

여러 데이터베이스 또는 시스템에 걸쳐 있는 트랜잭션을 처리하기 위한 방법.

  1. 2 단계 커밋 (Two-Phase Commit)
    분산 트랜잭션의 일관성을 보장하기 위한 프로토콜로, 준비 단계와 커밋 단계로 구성된다:
    1. 준비 단계: 모든 참여 노드가 트랜잭션을 커밋할 준비가 되었는지 확인
    2. 커밋 단계: 모든 노드가 준비되었다면 실제 커밋 실행
  2. 보상 트랜잭션 (Compensating Transaction)
    마이크로서비스 아키텍처에서 주로 사용되는 방식으로, 트랜잭션이 실패할 경우 이미 수행된 작업을 취소하는 별도의 트랜잭션을 실행한다.

## 용어 정리

용어설명

참고 및 출처