트랜잭션 제어
트랜잭션을 시작하고, 확정하고, 취소하는 명령어를 다룹니다. Oracle과 MySQL은 트랜잭션 시작 방식이 다르므로 주의가 필요합니다. 정확한 트랜잭션 제어를 모르면 데이터가 의도치 않게 확정되거나 유실될 수 있습니다.
COMMIT과 ROLLBACK
COMMIT은 트랜잭션의 모든 변경을 확정(영구 반영)합니다. ROLLBACK은 트랜잭션의 모든 변경을 취소하여 원래 상태로 되돌립니다.
-- Oracle은 DML 실행 시 자동으로 트랜잭션 시작 (수동 커밋 모드)
UPDATE accounts SET balance = balance - 100000 WHERE id = 1;
UPDATE accounts SET balance = balance + 100000 WHERE id = 2;
-- 성공하면 확정
COMMIT;
-- 또는 문제가 있으면 취소
ROLLBACK;-- 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 실행 시
1. 리두 로그(WAL) 버퍼를 디스크에 플러시 (durability 보장)
2. 언두 세그먼트의 해당 트랜잭션 정보를 "커밋됨"으로 표시
3. 보유 중인 모든 잠금(Lock) 해제
4. 다른 세션에서 변경된 데이터를 볼 수 있게 됨
ROLLBACK 실행 시
1. 언두 세그먼트의 이전 이미지를 사용하여 데이터 복원
2. 변경된 블록을 원래 값으로 되돌림
3. 보유 중인 모든 잠금(Lock) 해제
4. 트랜잭션이 없었던 것처럼 원래 상태 복원| 명령어 | 동작 | 잠금 | 비용 |
|---|---|---|---|
| COMMIT | 모든 변경을 영구 반영 | 해제 | 로그 플러시 (빠름) |
| ROLLBACK | 모든 변경을 취소 | 해제 | 언두 적용 (느릴 수 있음) |
COMMIT
* 리두 로그만 디스크에 쓰면 완료
* 변경된 데이터 블록은 나중에 비동기 기록 (Checkpoint)
ROLLBACK
* 변경된 모든 블록에 언두 이미지를 다시 적용해야 함
* 대량 DML 후 ROLLBACK → 매우 오래 걸릴 수 있음
예: 100만 건 UPDATE 후
COMMIT → 수 밀리초 (로그 flush만)
ROLLBACK → 수 분 (100만 건 원복)Oracle vs MySQL 자동 커밋 차이
| 항목 | Oracle | MySQL (InnoDB) |
|---|---|---|
| 기본 모드 | 수동 커밋 | 자동 커밋 (autocommit=1) |
| 트랜잭션 시작 | DML 실행 시 자동 | START TRANSACTION 또는 BEGIN |
| COMMIT 하지 않으면 | 세션 종료 시 ROLLBACK | 각 DML이 즉시 자동 COMMIT |
| DDL 실행 시 | 암묵적 COMMIT 발생 | 암묵적 COMMIT 발생 |
Oracle에서 UPDATE 후 COMMIT을 안 하면 다른 세션에서 변경된 데이터를 볼 수 없고, 세션이 종료되면 변경이 취소됩니다. MySQL은 기본적으로 각 SQL이 자동 커밋되므로, 명시적 트랜잭션을 쓰려면 START TRANSACTION을 선언해야 합니다.
-- 현재 설정 확인
SELECT @@autocommit; -- 1 = 자동 커밋 ON
-- 세션 단위로 자동 커밋 끄기
SET autocommit = 0;
-- → 이후 모든 DML은 명시적 COMMIT/ROLLBACK 필요
-- → Oracle과 유사한 동작
-- 다시 켜기
SET autocommit = 1;
-- START TRANSACTION은 autocommit 설정과 관계없이
-- 무조건 명시적 트랜잭션 시작
START TRANSACTION;
-- autocommit=1이어도 COMMIT/ROLLBACK까지 하나의 트랜잭션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는 트랜잭션 중간에 체크포인트를 설정하여, 전체가 아닌 특정 지점까지만 롤백할 수 있게 합니다.
-- 주문 처리 트랜잭션
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;트랜잭션 시작
│
├─ INSERT orders ← 유지됨
│
├─ SAVEPOINT sp1 ← 체크포인트
│
├─ INSERT order_items ← 취소됨
│
├─ ROLLBACK TO sp1 ← sp1 이후만 되돌림
│
├─ INSERT order_items ← 새로 실행
│
└─ COMMIT ← 전체 확정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 제거-- 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)을 실행하면 현재 트랜잭션이 자동으로 커밋됩니다.
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도 트랜잭션 안에서 롤백 가능
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; -- 세 쿼리 모두 동일 시점의 데이터 보장트랜잭션 제어와 애플리케이션 패턴
실무에서 트랜잭션 제어를 잘못하면 데이터 정합성 문제나 성능 문제가 발생합니다.
잘못된 방법
START TRANSACTION;
-- 100만 건 INSERT/UPDATE
COMMIT;
→ 언두 공간 폭발, 롤백 시 수십 분 소요
올바른 방법
DECLARE @batch_size INT = 1000;
WHILE (처리할 데이터 존재)
START TRANSACTION;
-- 1000건씩 처리
COMMIT;
END WHILE
→ 중간에 실패해도 1000건만 롤백
→ 언두 공간 적게 사용
→ 잠금도 1000건 단위로만 유지잘못된 방법
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;
→ 트랜잭션 지속 시간 최소화주의할 점
* 커넥션 풀에서 커넥션을 받을 때
이전 사용자가 COMMIT/ROLLBACK 안 했을 수 있음
* 대부분의 풀은 반환 시 자동 ROLLBACK 수행
but 명시적으로 처리하는 것이 안전
* autocommit 설정이 커넥션별로 유지될 수 있음
→ 풀에서 가져온 커넥션의 autocommit 상태 확인 필요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에서는 현재 트랜잭션과 독립적인 별도 트랜잭션을 실행할 수 있습니다.
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에 의한 암묵적 커밋에 주의해야 합니다.
다음 절에서는 트랜잭션의 상태 전이와 직렬 가능성을 다루겠습니다.