한 줄 요약: ACID 는 트랜잭션이 지켜야 할 네 가지 계약임. InnoDB 는 Atomicity 를 undo log(롤백), Isolation 을 MVCC + lock(스냅샷 읽기), Durability 를 redo log(WAL) 로 구현함. Consistency 는 DB 제약 조건과 애플리케이션 불변식이 함께 지켜야 함. 네 속성이 함께 동작해야 비로소 트랜잭션이 안전하며, 어느 하나라도 설정이나 설계로 약화하면 데이터 정합성이 깨질 수 있음.
개요
트랜잭션은 하나의 논리적 작업 단위임. “이체” 처럼 여러 DML 이 묶여 있을 때, 중간에 실패하면 아무 일도 없었던 것처럼 되돌려야 하고, 성공하면 어떤 장애가 와도 데이터가 살아있어야 함. ACID 는 이 두 가지 보장을 네 속성으로 구체화한 계약임.
| 속성 | 보장 내용 | 주요 구현 / 책임 |
|---|---|---|
| Atomicity (원자성) | 트랜잭션 전체가 적용되거나 전혀 적용되지 않음 | undo log — ROLLBACK 시 이전 이미지로 복원 |
| Consistency (일관성) | 트랜잭션 전후 모두 데이터 무결성 규칙을 만족 | DB 제약 + 애플리케이션 비즈니스 규칙 |
| Isolation (격리성) | 동시 실행 트랜잭션이 서로의 중간 상태를 보지 않음 | MVCC(스냅샷 읽기) + row-level lock |
| Durability (내구성) | 커밋된 트랜잭션은 장애 후에도 유지됨 | redo log — WAL 원칙으로 크래시 복구 |
결론
왜 이런 구현인가
| 질문 | 답변 |
|---|---|
| 왜 롤백에 undo log 가 필요한가 | DML 은 buffer pool 의 페이지를 직접 수정함. 수정 전 이미지를 undo log 에 남겨두지 않으면 ROLLBACK 시 원래 값을 복원할 방법이 없음. |
| 왜 Consistency 는 “DB 만” 책임지지 않는가 | NOT NULL · FK · CHECK 는 DB 가 강제하지만, “잔액이 음수가 되면 안 된다” 같은 비즈니스 규칙은 애플리케이션이 직접 검증해야 함. DB 제약만으로는 부족함. |
| 왜 SELECT 가 UPDATE 를 막지 않는가 (MVCC) | undo log 가 수정 전 버전을 들고 있어, SELECT 는 잠금 없이 자신의 스냅샷 시점에 맞는 버전을 읽을 수 있음. SELECT 와 UPDATE 가 독립적으로 동작해 처리량이 유지됨. |
| 왜 커밋 후에도 데이터가 디스크에 없을 수 있나 | 성능을 위해 데이터 페이지는 buffer pool 에서 나중에 flush 됨. 단, redo log 는 커밋 시 먼저 디스크에 기록돼 크래시 후 재적용으로 복구 가능함. |
운영 시 기억할 것
innodb_flush_log_at_trx_commit=1유지 — 기본값이자 Durability 완전 보장 설정임. 2·0 으로 낮추면 OS/mysqld 크래시 시 커밋된 트랜잭션도 잃을 수 있음.- 롱 트랜잭션은 undo 회수를 지연시킴 — 오래 열린 consistent read 가 있으면 과거 버전을 지워야 하는 Purge 가 늦어져 undo tablespace 사용량과 MVCC 비용이 늘 수 있음.
- CHECK 제약은 8.0.16 부터 강제됨 — 이전 버전에서는 파서가 통과시키지만 강제하지 않았음. 업그레이드 전 기존 데이터의 위반 여부를 확인해야 함.
- REPEATABLE READ 기본 격리 수준 — InnoDB 기본값. 첫 번째 SELECT 시점에 스냅샷이 생성돼 같은 트랜잭션 안에서 항상 일관된 뷰를 제공함. 격리 수준별 동작 차이는 별도 글에서 다룸.
상세
A — Atomicity: undo log 와 ROLLBACK
트랜잭션 안의 DML 은 buffer pool 페이지를 직접 수정함. 각 수정 시 수정 전 이미지(before image) 가 undo log 에 기록됨. ROLLBACK 이 발생하면 InnoDB 는 undo log 를 역순으로 따라가며 모든 변경을 원래 값으로 되돌림.
크래시 복구에서도 같은 원리가 적용됨. redo log 로 커밋된 변경을 재적용(REDO phase)한 뒤, undo log 로 미완료 트랜잭션을 되돌림(UNDO phase).
SAVEPOINT 를 사용하면 트랜잭션 전체가 아닌 특정 지점까지만 롤백할 수 있음.
START TRANSACTION;
SAVEPOINT sp1;
-- sp1 이후 작업만 선택적으로 취소 가능
ROLLBACK TO sp1; -- sp1 이전 변경은 유지
COMMIT;undo log 는 ROLLBACK 외에도 MVCC 의 재료 로 쓰임. 다른 트랜잭션이 이전 버전의 행을 읽을 때 undo log record 에서 수정 전 데이터를 가져와 consistent read 를 구성함.
C — Consistency: DB 제약과 애플리케이션 책임
InnoDB 가 강제하는 제약은 다음과 같음.
| 제약 종류 | 강제 시점 | 위반 시 오류 번호 |
|---|---|---|
NOT NULL | INSERT / UPDATE 시 | 1048 |
UNIQUE | INSERT / UPDATE 시 | 1062 |
FOREIGN KEY | INSERT / UPDATE / DELETE 시 | 1452 / 1451 |
CHECK (8.0.16+) | INSERT / UPDATE 시 | 3819 |
제약이 트랜잭션 안에서 위반되면 해당 DML 만 실패하고 트랜잭션은 열린 채로 유지됨. ROLLBACK 은 자동으로 일어나지 않으므로 애플리케이션이 명시적으로 처리해야 함.
MySQL 8.0.16 이전의
CHECK는 선언해도 실제로 강제되지 않았으므로 무결성 보장 효과가 없었음. 8.0.16 이상부터는CHECK가 실제 제약으로 동작해 위반INSERT/UPDATE가 실패함. 따라서 예전 버전에서CHECK를 선언해 둔 테이블이라도 기존 데이터가 조건을 만족한다고 가정하면 안 됨.
Consistency 는 DB 제약만으로 완성되지 않음. “이체 후 두 계좌 잔액의 합이 변하지 않아야 한다”는 비즈니스 규칙은 DB 가 모름 — 애플리케이션이 트랜잭션 안에서 직접 검증해야 함.
I — Isolation: MVCC + lock
MVCC(Multi-Version Concurrency Control) 는 같은 행의 여러 버전을 동시에 유지하는 방식임. SELECT 는 자신의 스냅샷 시점에 맞는 버전을 잠금 없이 읽고, UPDATE 는 최신 버전을 잠가서 수정함. 덕분에 SELECT 와 UPDATE 가 서로를 차단하지 않음.
InnoDB 의 기본 격리 수준은 REPEATABLE READ 임. 트랜잭션 안에서 첫 번째 consistent read(잠금 없는 SELECT) 가 실행되는 시점에 스냅샷이 생성됨. 이후 다른 트랜잭션이 변경하고 커밋해도 같은 트랜잭션 안에서는 스냅샷 시점 데이터를 계속 봄.
sequenceDiagram participant T1 as 트랜잭션 1 participant DB as InnoDB participant T2 as 트랜잭션 2 T1->>DB: START TRANSACTION T1->>DB: SELECT balance (→ 70, 스냅샷 생성) T2->>DB: UPDATE balance = 999 T2->>DB: COMMIT T1->>DB: SELECT balance (→ 70, 스냅샷 유지) T1->>DB: COMMIT
스냅샷 읽기(consistent read)와 달리 SELECT ... FOR UPDATE / FOR SHARE, UPDATE, DELETE 는 current read 로, 항상 최신 커밋 버전을 읽고 행에 잠금을 걸음.
격리 수준(REPEATABLE READ / READ COMMITTED / SERIALIZABLE 등)에 따라 스냅샷 생성 시점과 잠금 범위가 달라짐.
D — Durability: redo log 와 WAL
WAL(Write-Ahead Logging) 원칙: 데이터 페이지를 디스크에 쓰기 전에 redo log 를 먼저 디스크에 기록함. 커밋 시 buffer pool 의 dirty page 가 아직 .ibd 파일에 없어도, redo log 가 있으면 크래시 후 재적용으로 복구 가능함.
redo log 가 디스크에 내려가는 시점은 innodb_flush_log_at_trx_commit 이 결정함.
| 값 | 동작 | 크래시 시 손실 가능 |
|---|---|---|
1 (기본값) | 커밋마다 write + flush | 정상 fsync 기준 없음 |
2 | 커밋마다 OS 버퍼에 write, 1초마다 flush | OS 크래시 시 최대 1초 |
0 | 1초마다 write + flush | mysqld/OS 크래시 시 최대 1초 |
스토리지나 OS 가 fsync() 완료를 거짓으로 보고하면 1 에서도 하드웨어 수준 내구성은 깨질 수 있음. MySQL 설정 관점에서는 1 이 full ACID compliance 에 필요한 기본값임.
redo log 내부에서는 변경 record 가 LSN 증가 순서로 append 되고, checkpoint 가 진행되면서 오래된 redo 가 잘려 나감.
테스트
테스트 환경: MySQL 8.4.8 (
mysql:8.4이미지, 2026-05-29 직접 확인)
SELECT VERSION() AS mysql_version,
@@transaction_isolation AS isolation,
@@innodb_flush_log_at_trx_commit AS flush_log_at_trx_commit;mysql_version isolation flush_log_at_trx_commit
8.4.8 REPEATABLE-READ 1-- 사전 준비
CREATE TABLE accounts (
id INT PRIMARY KEY,
name VARCHAR(20),
balance INT NOT NULL,
CONSTRAINT chk_balance CHECK (balance >= 0)
);
CREATE TABLE transfers (
id INT AUTO_INCREMENT PRIMARY KEY,
from_id INT, to_id INT, amount INT,
FOREIGN KEY (from_id) REFERENCES accounts(id),
FOREIGN KEY (to_id) REFERENCES accounts(id)
);
INSERT INTO accounts VALUES (1, 'Alice', 100), (2, 'Bob', 50);1. Atomicity — SAVEPOINT 부분 롤백
START TRANSACTION;
UPDATE accounts SET balance = balance - 30 WHERE id = 1; -- Alice: 100 → 70
SAVEPOINT sp1;
UPDATE accounts SET balance = balance + 30 WHERE id = 2; -- Bob: 50 → 80
SELECT id, name, balance FROM accounts;id name balance
1 Alice 70
2 Bob 80ROLLBACK TO sp1; -- Bob 변경만 취소, Alice 변경은 유지
SELECT id, name, balance FROM accounts;id name balance
1 Alice 70
2 Bob 50COMMIT;
SELECT id, name, balance FROM accounts;id name balance
1 Alice 70
2 Bob 50ROLLBACK TO sp1 은 sp1 이후의 Bob 변경만 되돌리고 Alice 변경은 유지함. COMMIT 으로 Alice 변경이 확정됨.
2. Consistency — CHECK · FK 제약 위반
START TRANSACTION;
UPDATE accounts SET balance = balance + 5 WHERE id = 2;
-- CHECK 위반: balance 가 음수가 되는 UPDATE
UPDATE accounts SET balance = balance - 999 WHERE id = 1;
SELECT id, name, balance FROM accounts WHERE id = 2;
ROLLBACK;
SELECT id, name, balance FROM accounts WHERE id = 2;ERROR 3819 (HY000): Check constraint 'chk_balance' is violated.
id name balance
2 Bob 55
id name balance
2 Bob 50START TRANSACTION;
-- FK 위반: 존재하지 않는 account(id=99) 참조
INSERT INTO transfers (from_id, to_id, amount) VALUES (1, 99, 10);
SELECT COUNT(*) AS transfers_count FROM transfers;
ROLLBACK;ERROR 1452 (23000): Cannot add or update a child row:
a foreign key constraint fails (`testdb`.`transfers`,
CONSTRAINT `transfers_ibfk_2` FOREIGN KEY (`to_id`) REFERENCES `accounts` (`id`))
transfers_count
0명시 트랜잭션 안에서는 제약 위반 DML 만 실패하고 트랜잭션은 열린 채 유지됨. 실패 전에 실행한 Bob 의 +5 변경은 같은 세션 안에서 계속 보이며, 애플리케이션이 이후 ROLLBACK 또는 COMMIT 을 명시해야 함.
3. Isolation — MVCC REPEATABLE READ 스냅샷
두 세션을 동시에 실행한 결과임.
세션 T1 (먼저 SELECT, SLEEP 중 T2 커밋 후 다시 SELECT):
START TRANSACTION;
SELECT id, name, balance FROM accounts WHERE id = 1; -- 스냅샷 생성
-- (T2 가 balance = 999 로 커밋한 뒤)
SELECT id, name, balance FROM accounts WHERE id = 1; -- 스냅샷 유지
COMMIT;-- 첫 번째 SELECT
id name balance
1 Alice 70
-- 두 번째 SELECT (T2 커밋 이후)
id name balance
1 Alice 70세션 T2 (T1 이 열려있는 동안 UPDATE + COMMIT):
UPDATE accounts SET balance = 999 WHERE id = 1;
COMMIT;
SELECT id, name, balance FROM accounts WHERE id = 1;id name balance
1 Alice 999T2 가 999 로 커밋했지만 T1 은 스냅샷 시점인 70 을 계속 봄. T2 의 직접 조회는 최신값 999 를 반환함 — T1 과 T2 가 독립된 뷰를 가짐.
참고
- MySQL 8.4 — InnoDB and the ACID Model — A·C·I·D 각 속성이 InnoDB 에서 보장되는 방식
- MySQL 8.4 — Consistent Nonlocking Reads — REPEATABLE READ 스냅샷 생성 시점과 nonlocking SELECT 동작
- MySQL 8.4 — InnoDB Transaction Model — 트랜잭션 모델·격리 수준 개요
- MySQL 8.4 — SAVEPOINT, ROLLBACK TO SAVEPOINT, and RELEASE SAVEPOINT — SAVEPOINT 문법과 제약 사항
- MySQL 8.0.16 Release Notes — “Previously, MySQL permitted a limited form of CHECK constraint syntax, but parsed and ignored it” — 8.0.16 에서 CHECK 강제 시행 시작 근거
- MySQL 8.4 — CHECK Constraints — CHECK 문법·ENFORCED / NOT ENFORCED 옵션
- MySQL 8.4 — Undo Logs — undo log record 와 consistent read 의 관계
- MySQL 8.4 — Redo Log — redo log file·LSN·checkpoint 관계
- MySQL 8.4 —
innodb_flush_log_at_trx_commit— 0·1·2 설정별 flush 시점과 Durability 보장 범위