icon

안동민 개발노트

10장 : 트랜잭션

트랜잭션 제어


트랜잭션을 시작하고, 확정하고, 취소하는 명령어를 다룹니다. Oracle과 MySQL은 트랜잭션 시작 방식이 다르므로 주의가 필요합니다. 정확한 트랜잭션 제어를 모르면 데이터가 의도치 않게 확정되거나 유실될 수 있습니다.


COMMIT과 ROLLBACK

COMMIT은 트랜잭션의 모든 변경을 확정(영구 반영)합니다. ROLLBACK은 트랜잭션의 모든 변경을 취소하여 원래 상태로 되돌립니다.

기본 트랜잭션 제어 (Oracle)
-- Oracle은 DML 실행 시 자동으로 트랜잭션 시작 (수동 커밋 모드)
UPDATE accounts SET balance = balance - 100000 WHERE id = 1;
UPDATE accounts SET balance = balance + 100000 WHERE id = 2;

-- 성공하면 확정
COMMIT;

-- 또는 문제가 있으면 취소
ROLLBACK;
기본 트랜잭션 제어 (MySQL)
-- MySQL InnoDB는 기본 자동 커밋 (autocommit=1)
-- 명시적 트랜잭션:
START TRANSACTION;

UPDATE accounts SET balance = balance - 100000 WHERE id = 1;
UPDATE accounts SET balance = balance + 100000 WHERE id = 2;

COMMIT;
-- 또는
ROLLBACK;
COMMIT과 ROLLBACK의 내부 동작
COMMIT 실행 시
  1. 리두 로그(WAL) 버퍼를 디스크에 플러시 (durability 보장)
  2. 언두 세그먼트의 해당 트랜잭션 정보를 "커밋됨"으로 표시
  3. 보유 중인 모든 잠금(Lock) 해제
  4. 다른 세션에서 변경된 데이터를 볼 수 있게 됨

ROLLBACK 실행 시
  1. 언두 세그먼트의 이전 이미지를 사용하여 데이터 복원
  2. 변경된 블록을 원래 값으로 되돌림
  3. 보유 중인 모든 잠금(Lock) 해제
  4. 트랜잭션이 없었던 것처럼 원래 상태 복원
명령어동작잠금비용
COMMIT모든 변경을 영구 반영해제로그 플러시 (빠름)
ROLLBACK모든 변경을 취소해제언두 적용 (느릴 수 있음)
ROLLBACK이 COMMIT보다 느린 이유
COMMIT
  * 리두 로그만 디스크에 쓰면 완료
  * 변경된 데이터 블록은 나중에 비동기 기록 (Checkpoint)

ROLLBACK
  * 변경된 모든 블록에 언두 이미지를 다시 적용해야 함
  * 대량 DML 후 ROLLBACK → 매우 오래 걸릴 수 있음

예: 100만 건 UPDATE 후
  COMMIT → 수 밀리초 (로그 flush만)
  ROLLBACK → 수 분 (100만 건 원복)

Oracle vs MySQL 자동 커밋 차이

항목OracleMySQL (InnoDB)
기본 모드수동 커밋자동 커밋 (autocommit=1)
트랜잭션 시작DML 실행 시 자동START TRANSACTION 또는 BEGIN
COMMIT 하지 않으면세션 종료 시 ROLLBACK각 DML이 즉시 자동 COMMIT
DDL 실행 시암묵적 COMMIT 발생암묵적 COMMIT 발생

Oracle에서 UPDATE 후 COMMIT을 안 하면 다른 세션에서 변경된 데이터를 볼 수 없고, 세션이 종료되면 변경이 취소됩니다. MySQL은 기본적으로 각 SQL이 자동 커밋되므로, 명시적 트랜잭션을 쓰려면 START TRANSACTION을 선언해야 합니다.

MySQL 자동 커밋 제어
-- 현재 설정 확인
SELECT @@autocommit;  -- 1 = 자동 커밋 ON

-- 세션 단위로 자동 커밋 끄기
SET autocommit = 0;
-- → 이후 모든 DML은 명시적 COMMIT/ROLLBACK 필요
-- → Oracle과 유사한 동작

-- 다시 켜기
SET autocommit = 1;

-- START TRANSACTION은 autocommit 설정과 관계없이
-- 무조건 명시적 트랜잭션 시작
START TRANSACTION;
-- autocommit=1이어도 COMMIT/ROLLBACK까지 하나의 트랜잭션
DBMS별 트랜잭션 시작 구문
Oracle
  * 별도 시작 구문 없음, DML 실행 시 자동 시작
  * SET TRANSACTION READ ONLY; (읽기 전용 트랜잭션)
  * SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

MySQL
  * START TRANSACTION; 또는 BEGIN;
  * START TRANSACTION READ ONLY;
  * START TRANSACTION WITH CONSISTENT SNAPSHOT;

PostgreSQL
  * BEGIN; 또는 START TRANSACTION;
  * BEGIN ISOLATION LEVEL REPEATABLE READ;

SQL Server
  * BEGIN TRANSACTION; (또는 BEGIN TRAN;)
  * SET IMPLICIT_TRANSACTIONS ON; (Oracle처럼 수동 커밋)

SAVEPOINT와 부분 롤백

SAVEPOINT는 트랜잭션 중간에 체크포인트를 설정하여, 전체가 아닌 특정 지점까지만 롤백할 수 있게 합니다.

SAVEPOINT 사용 (Oracle)
-- 주문 처리 트랜잭션
INSERT INTO orders (id, user_id, total)
VALUES (orders_seq.NEXTVAL, 1, 50000);

SAVEPOINT after_order;

INSERT INTO order_items (id, order_id, product_id, quantity, price)
VALUES (items_seq.NEXTVAL, orders_seq.CURRVAL, 101, 2, 25000);

-- 재고 부족 발견!
ROLLBACK TO after_order;
-- → order_items INSERT만 취소, orders INSERT는 유지

-- 다른 상품으로 대체
INSERT INTO order_items (id, order_id, product_id, quantity, price)
VALUES (items_seq.NEXTVAL, orders_seq.CURRVAL, 102, 1, 50000);

COMMIT;
SAVEPOINT 흐름
트랜잭션 시작

  ├─ INSERT orders      ← 유지됨

  ├─ SAVEPOINT sp1      ← 체크포인트

  ├─ INSERT order_items ← 취소됨

  ├─ ROLLBACK TO sp1    ← sp1 이후만 되돌림

  ├─ INSERT order_items ← 새로 실행

  └─ COMMIT             ← 전체 확정

SAVEPOINT 규칙

SAVEPOINT 주의사항
1. ROLLBACK TO sp1 후에도 트랜잭션은 계속 진행 중
   → 반드시 최종 COMMIT 또는 ROLLBACK 필요

2. 같은 이름으로 SAVEPOINT 재선언 가능
   → 이전 SAVEPOINT는 덮어씌워짐

3. ROLLBACK TO sp1 하면 sp1 이후 선언된 SAVEPOINT도 삭제됨
   SAVEPOINT sp1;
   -- 작업 A --
   SAVEPOINT sp2;
   -- 작업 B --
   ROLLBACK TO sp1;  → sp2도 삭제됨

4. 전체 ROLLBACK은 모든 SAVEPOINT 제거

5. COMMIT은 모든 SAVEPOINT 제거
SAVEPOINT DBMS별 구문
-- Oracle
SAVEPOINT point_name;
ROLLBACK TO point_name;

-- MySQL
SAVEPOINT point_name;
ROLLBACK TO SAVEPOINT point_name;
RELEASE SAVEPOINT point_name;  -- 명시적 삭제

-- PostgreSQL
SAVEPOINT point_name;
ROLLBACK TO SAVEPOINT point_name;
RELEASE SAVEPOINT point_name;

-- SQL Server (트랜잭션 이름 방식)
SAVE TRANSACTION point_name;
ROLLBACK TRANSACTION point_name;

DDL과 암묵적 커밋

Oracle과 MySQL 모두 DDL(CREATE, ALTER, DROP)을 실행하면 현재 트랜잭션이 자동으로 커밋됩니다.

암묵적 커밋의 함정 (Oracle)
UPDATE accounts SET balance = balance - 100000 WHERE id = 1;
-- 아직 COMMIT 안 함

CREATE TABLE temp_log (msg VARCHAR2(200));
-- DDL 실행 → 암묵적 COMMIT 발생!
-- 위의 UPDATE가 자동으로 확정됨

ROLLBACK;
-- 이미 커밋되었으므로 롤백 불가!
암묵적 커밋이 발생하는 경우
Oracle
  * DDL 실행 (CREATE, ALTER, DROP, TRUNCATE 등)
  * DCL 실행 (GRANT, REVOKE)
  * 정상적인 세션 종료 (exit, quit)
  * DDL은 실행 전후 두 번 커밋
    → DDL 실패해도 이전 DML은 이미 커밋됨!

MySQL InnoDB
  * DDL 실행
  * autocommit=1 상태에서 각 DML 문
  * LOCK TABLES, UNLOCK TABLES
  * START TRANSACTION (이전 트랜잭션 암묵적 커밋)

PostgreSQL
  * DDL이 트랜잭션 내에서 실행 가능 (롤백 가능!)
  * CREATE TABLE, DROP TABLE도 ROLLBACK 가능
  * 이것은 PostgreSQL만의 큰 장점
PostgreSQL의 트랜잭셔널 DDL
-- PostgreSQL: DDL도 트랜잭션 안에서 롤백 가능
BEGIN;

CREATE TABLE new_table (id SERIAL, name TEXT);
INSERT INTO new_table (name) VALUES ('test');

-- 확인 후 문제 발견
ROLLBACK;
-- → CREATE TABLE도 취소됨! (Oracle/MySQL에서는 불가)

트랜잭션 제어와 잠금

트랜잭션이 끝나기 전까지(COMMIT 또는 ROLLBACK) 변경한 행에 대한 잠금이 유지됩니다.

트랜잭션과 잠금의 관계
세션 A
  UPDATE accounts SET balance = 0 WHERE id = 1;
  -- → id=1 행에 배타적 잠금(X Lock) 획득
  -- → COMMIT/ROLLBACK 할 때까지 잠금 유지

세션 B
  UPDATE accounts SET balance = 500 WHERE id = 1;
  -- → id=1 잠금 대기... (블로킹)
  -- → 세션 A가 COMMIT 또는 ROLLBACK 할 때까지 기다림

결론: 긴 트랜잭션 = 긴 잠금 = 동시성 저하
트랜잭션 잠금 관련 실무 원칙
1. 트랜잭션은 가능한 짧게 유지
   → 잠금 시간을 최소화하여 동시성 확보

2. 사용자 입력을 기다리는 중에 트랜잭션을 열어두지 않기
   → "SELECT ... FOR UPDATE" 후 사용자 확인 대기 → 위험!

3. 대량 DML은 배치로 나누어 중간 COMMIT
   → 100만 건 UPDATE를 한 트랜잭션으로 → 언두/로그 폭증
   → 1000건씩 나누어 COMMIT → 안전

4. COMMIT 빈도와 데이터 정합성의 균형
   → 너무 잦은 COMMIT → 원자성 깨질 수 있음
   → 너무 드문 COMMIT → 잠금 + 리소스 부담

읽기 전용 트랜잭션과 읽기/쓰기 트랜잭션

읽기 전용 트랜잭션
-- Oracle
SET TRANSACTION READ ONLY;
-- → SELECT만 허용, DML 실행 시 에러
-- → 트랜잭션 시작 시점의 일관된 스냅샷 읽기 (Consistent Read)

-- MySQL
START TRANSACTION READ ONLY;
-- → InnoDB 최적화: 트랜잭션 ID 할당 생략 → 성능 향상

-- PostgreSQL
BEGIN TRANSACTION READ ONLY;

-- SQL Server
SET TRANSACTION ISOLATION LEVEL SNAPSHOT;
BEGIN TRANSACTION;
-- → 읽기 전용으로 명시하는 별도 구문은 없으나
-- → SNAPSHOT 격리로 유사한 효과
읽기 전용 트랜잭션의 이점
1. 일관된 스냅샷: 트랜잭션 시작 시점의 데이터를 일관되게 조회
   → 장기 조회 중 다른 트랜잭션이 데이터를 변경해도
     조회 결과는 변하지 않음

2. 성능 최적화: DML 불가능하므로 잠금/언두 오버헤드 감소

3. 보고서 생성 시 유용:
   BEGIN READ ONLY;
   SELECT ... -- 대시보드 데이터 조회 1
   SELECT ... -- 대시보드 데이터 조회 2
   SELECT ... -- 대시보드 데이터 조회 3
   COMMIT;    -- 세 쿼리 모두 동일 시점의 데이터 보장

트랜잭션 제어와 애플리케이션 패턴

실무에서 트랜잭션 제어를 잘못하면 데이터 정합성 문제나 성능 문제가 발생합니다.

패턴 1: 대량 배치 처리
잘못된 방법
  START TRANSACTION;
  -- 100만 건 INSERT/UPDATE
  COMMIT;
  → 언두 공간 폭발, 롤백 시 수십 분 소요

올바른 방법
  DECLARE @batch_size INT = 1000;
  WHILE (처리할 데이터 존재)
    START TRANSACTION;
    -- 1000건씩 처리
    COMMIT;
  END WHILE
  → 중간에 실패해도 1000건만 롤백
  → 언두 공간 적게 사용
  → 잠금도 1000건 단위로만 유지
패턴 2: 트랜잭션과 외부 시스템 연동
잘못된 방법
  START TRANSACTION;
  UPDATE orders SET status = 'PAID';
  -- 외부 결제 API 호출 (5초 소요)
  -- → 5초 동안 행 잠금 유지!
  COMMIT;

올바른 방법
  -- 1단계: 외부 API 먼저 호출 (트랜잭션 밖)
  결제결과 = 결제API.호출();
  
  -- 2단계: 성공 시 짧은 트랜잭션으로 DB 업데이트
  START TRANSACTION;
  UPDATE orders SET status = 'PAID', payment_id = ?;
  INSERT INTO payment_log (...) VALUES (...);
  COMMIT;
  → 트랜잭션 지속 시간 최소화
패턴 3: Connection Pool과 트랜잭션
주의할 점
  * 커넥션 풀에서 커넥션을 받을 때
    이전 사용자가 COMMIT/ROLLBACK 안 했을 수 있음
  
  * 대부분의 풀은 반환 시 자동 ROLLBACK 수행
    but 명시적으로 처리하는 것이 안전

  * autocommit 설정이 커넥션별로 유지될 수 있음
    → 풀에서 가져온 커넥션의 autocommit 상태 확인 필요

DBMS별 트랜잭션 제어 비교 종합

DBMS별 동작 비교
┌───────────────────┬──────────┬──────────┬──────────┬───────────┐
│       기능        │ Oracle   │ MySQL    │PostgreSQL│ SQL Server│
├───────────────────┼──────────┼──────────┼──────────┼───────────┤
│ 기본 커밋 모드    │   수동   │   자동   │   자동   │   자동    │
│ 트랜잭션 시작     │  암묵적  │ START TX │ BEGIN    │ BEGIN TRAN│
│ DDL 트랜잭션      │   불가   │   불가   │   가능   │   가능    │
│ SAVEPOINT         │   지원   │   지원   │   지원   │ SAVE TRAN │
│ 읽기 전용 TX      │ SET TX   │ START TX │ BEGIN    │ SNAPSHOT  │
│        2PC        │   지원   │    XA    │ PREPARE  │ MS DTC    │
│ Autonomous TX     │ PRAGMA   │  미지원  │  미지원  │  미지원   │
└───────────────────┴──────────┴──────────┴──────────┴───────────┘

자율 트랜잭션 (Autonomous Transaction)

Oracle에서는 현재 트랜잭션과 독립적인 별도 트랜잭션을 실행할 수 있습니다.

Oracle 자율 트랜잭션
CREATE OR REPLACE PROCEDURE log_event(p_msg VARCHAR2) IS
    PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
    INSERT INTO event_log (msg, log_time)
    VALUES (p_msg, SYSDATE);
    COMMIT;  -- 메인 트랜잭션과 독립적으로 커밋
END;

-- 메인 트랜잭션
BEGIN
    UPDATE accounts SET balance = balance - 100 WHERE id = 1;
    log_event('출금 시도');  -- 별도 트랜잭션으로 로그 기록
    
    -- 에러 발생!
    ROLLBACK;
    -- → accounts UPDATE는 롤백
    -- → event_log INSERT는 이미 커밋되어 유지됨!
END;
자율 트랜잭션 활용 사례
* 감사(Audit) 로그: 메인 트랜잭션 실패해도 로그는 남겨야 함
* 에러 로그: 예외 발생 시 에러 정보를 DB에 기록
* 시퀀스 관리: 채번은 롤백되면 안 됨

다른 DBMS에서의 대안
  MySQL: 별도 커넥션으로 로그 INSERT
  PostgreSQL: dblink 또는 별도 커넥션

트랜잭션 제어 흐름 정리

트랜잭션 상태 전이 다이어그램
 [시작] ─── DML 실행 ──→ [활동(Active)]

              ┌───────────────┼───────────────┐
              │               │               │
          정상 완료       에러 발생        SAVEPOINT로
              │               │           부분 롤백
              ▼               ▼               │
     [부분 확정(Partially   [실패(Failed)]    │
      Committed)]             │               │
              │               │               │
          COMMIT           ROLLBACK           │
              │               │               │
              ▼               ▼               │
       [확정(Committed)] [철회(Aborted)]      │
              │               │               │
              └───────┬───────┘               │
                      ▼                       │
                   [종료]                     │
                      └────── 재시작 가능 ────┘
트랜잭션 제어 명령어 요약
┌────────────────────┬─────────────────────────────────┐
│       명령어       │              동작               │
├────────────────────┼─────────────────────────────────┤
│ COMMIT             │ 변경 확정, 잠금 해제            │
│ ROLLBACK           │ 변경 취소, 잠금 해제            │
│ SAVEPOINT name     │ 중간 체크포인트 설정            │
│ ROLLBACK TO name   │ SAVEPOINT까지만 부분 롤백       │
│ SET TRANSACTION    │ 트랜잭션 특성 설정 (격리 수준)  │
│ START TRANSACTION  │ 명시적 트랜잭션 시작 (MySQL)    │
│ BEGIN              │ 트랜잭션 시작 (PostgreSQL)      │
│ BEGIN TRANSACTION  │ 트랜잭션 시작 (SQL Server)      │
└────────────────────┴─────────────────────────────────┘

정리

개념핵심 내용
COMMIT변경 확정, 로그 flush로 빠름
ROLLBACK변경 취소, 언두 적용으로 느릴 수 있음
SAVEPOINT트랜잭션 중간 체크포인트, 부분 롤백 가능
자동 커밋Oracle=수동, MySQL=자동(기본)
암묵적 커밋DDL 실행 시 자동 커밋 (PostgreSQL 제외)
잠금 유지COMMIT/ROLLBACK까지 잠금 유지
읽기 전용일관된 스냅샷 읽기, DML 차단

트랜잭션 제어의 핵심은 짧고 명확한 트랜잭션을 유지하는 것입니다. 트랜잭션이 길어지면 잠금이 오래 유지되어 동시성이 저하되고, 대량 변경 후 ROLLBACK은 매우 비용이 큽니다. 항상 COMMIT과 ROLLBACK을 명시적으로 제어하고, DDL에 의한 암묵적 커밋에 주의해야 합니다.

다음 절에서는 트랜잭션의 상태 전이직렬 가능성을 다루겠습니다.

목차