테마
트랜잭션 기초와 주의사항
트랜잭션(transaction)은 여러 작업을 하나로 묶는 단위다. 묶인 작업은 전부 반영되거나 전부 취소된다. 중간에 문제가 생기면 롤백(rollback)해 전부 없던 일로 되돌린다. ACID(Atomicity·Consistency·Isolation·Durability, 원자성·일관성·격리성·지속성)는 트랜잭션이 지켜야 할 네 가지 약속의 이름이다. 실무에서는 MySQL에서 어떤 문장이 트랜잭션을 끊는지 아는 것이 중요하다.
ACID 한눈에
트랜잭션의 변경을 확정하는 것을 커밋(commit), 모두 취소하고 시작 전 상태로 되돌리는 것을 롤백(rollback)이라 한다. InnoDB는 네 성질을 몇 가지 장치로 지킨다. 언두 로그(undo log)는 행을 바꾸기 전의 옛 값을 적어 둔 기록으로, 롤백할 때 이 값으로 되돌린다. 리두 로그(redo log)는 무엇을 바꿨는지 먼저 적어 두는 기록으로, 장애 뒤 재시작할 때 이를 다시 적용해 커밋된 변경을 살린다. MVCC(Multi-Version Concurrency Control, 다중 버전 동시성 제어)는 행의 옛 버전을 남겨 두고, 읽는 쪽이 자기에게 보여야 할 버전을 골라 읽게 하는 방식이다.
| 속성 | 뜻 | InnoDB의 구현 |
|---|---|---|
| 원자성(Atomicity) | 전부 되거나 전부 안 되거나 | 실패 시 언두 로그로 롤백 |
| 일관성(Consistency) | 규칙(제약 조건(constraint)·트리거)을 깨지 않음 | 제약 검사 + 원자·격리·지속의 결과 |
| 격리성(Isolation) | 섞어 돌려도 간섭 없음 | MVCC + 잠금(→ 격리 수준) |
| 지속성(Durability) | 커밋되면 장애에도 유지 | 리두 로그 + innodb_flush_log_at_trx_commit1 |
기본 흐름
sql
START TRANSACTION; -- 또는 BEGIN
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT; -- 확정. ROLLBACK이면 두 UPDATE 모두 취소- MySQL은 기본으로 자동 커밋(autocommit) 모드가 켜져 있다(
autocommit=1). 그래서 트랜잭션을 따로 열지 않으면 문장 하나하나가 실행 즉시 커밋된다. START TRANSACTION으로 연 트랜잭션은 자동 커밋 설정과 무관하게, 명시적으로COMMIT이나ROLLBACK을 실행할 때까지 한 묶음이다.SAVEPOINT sp1로 트랜잭션 중간에 세이브포인트(savepoint)를 찍어 두면,ROLLBACK TO sp1으로 그 지점 이후의 변경만 부분 취소할 수 있다.
주의사항: 트랜잭션을 끊는 것들
1. 암시적 커밋(implicit commit) — 대부분의 DDL(Data Definition Language, 데이터 정의어)은 트랜잭션을 자른다. 암시적 커밋은 사용자가 COMMIT을 쓰지 않았는데 서버가 알아서 커밋하는 것이다. DDL은 테이블·인덱스 같은 구조를 만들고 바꾸는 SQL이다. ALTER TABLE, CREATE INDEX, TRUNCATE, DROP 등은 실행 전후로 자동 커밋된다. 그래서 DDL 앞에서 해 둔 변경도 함께 확정되어 되돌릴 수 없다. CREATE TEMPORARY TABLE·DROP TEMPORARY TABLE은 암시적 커밋을 일으키지 않는 예외지만, 그렇다고 트랜잭션으로 되돌릴 수 있는 DDL이라는 뜻은 아니다. 그러므로 스키마(테이블 구조) 변경과 데이터 변경은 한 트랜잭션에 섞지 말고 분리한다.
2. 긴 트랜잭션은 비싸다. 열어 둔 트랜잭션은 자기가 아직 볼 수 있는 옛 버전이 담긴 언두 로그를 붙잡는다. 그러면 MVCC가 과거 버전을 정리하는 퍼지(purge) 작업을 하지 못해 테이블·인덱스가 부푼다. 배치 작업이라도 한 트랜잭션으로 몰지 말고 커밋 단위를 나눈다. 트랜잭션 범위는 필요한 만큼만 잡는 것이 철칙이다.
3. 읽기만 해도 격리 수준(isolation level) 비용이 있다.REPEATABLE READ는 InnoDB의 기본 격리 수준으로, 트랜잭션 안에서 같은 행을 다시 읽어도 처음 본 값이 보이게 한다. 이를 위해 장시간 읽기 트랜잭션도 첫 읽기 때 만든 스냅샷(snapshot)을 끝까지 유지한다. READ COMMITTED는 문장마다 새 스냅샷을 만드는 격리 수준이다. 대시보드용 장시간 SELECT라면 READ COMMITTED를 쓰거나, 트랜잭션 없이 자동 커밋 상태에서 한 건씩 조회하는 방법을 고려한다.
4. SELECT ... FOR UPDATE는 읽기이자 잠금이다. 재고 차감처럼 조회 결과를 근거로 바로 갱신할 때 쓴다. 일반 SELECT는 스냅샷의 버전을 읽지만, FOR UPDATE는 현재 읽기(current read)로 가장 최신 버전을 읽고 그 행에 잠금을 건다.
면접 포인트
- "DDL 뒤에 ROLLBACK이 되나요?" → 안 된다. 암시적 커밋 때문이다.
- "긴 트랜잭션이 왜 문제인가요?" → 언두 로그 증가 + MVCC 퍼지 지연 + 잠금 점유 시간. 커밋 단위 분할로 답한다.
- ACID를 줄줄 외우기보다 각 속성이 깨졌을 때 일어나는 일로 설명하면 설득력이 생긴다.
참고
커밋할 때 리두 로그를 디스크에 언제 확실히 기록할지 정하는 설정이다. 이 값이 지속성을 얼마나 엄격하게 지킬지를 좌우한다. ↩︎
