6장. 규칙은 깨지지 않는다 — 일관성

만화로 보는 요약 — 먼저 읽어보세요
모든 작업이 끝나도 결과가 틀릴 수 있다 → 일관성은 트랜잭션 전후로 불변식이 깨지지 않는 약속이다 → DB 제약과 애플리케이션 로직이 함께 지킨다.
면접 실전 질문: ① 원자성과 일관성의 차이 ② 불변식과 DB 제약의 역할 ③ 일관성이 공동 책임인 이유
배경 — “전부 됐다”와 “올바르다”는 다르다
5장 끝에서 던진 질문을 다시 볼게요. 네 개 작업이 전부 성공했는데, 그 결과가
재고 -3처럼 말이 안 되는 상태라면? 원자성은 만족했어요 — 전부 됐으니까.
그런데 데이터는 엉망입니다.
여기서 원자성과 일관성의 경계가 드러납니다. 원자성은 “전부 되거나 전부 안 되거나” 만 보장하지, 그 결과가 올바른지는 신경 쓰지 않아요. “올바른 상태를 유지한다”는 전혀 다른 약속이고, 그게 ACID의 C, 일관성(Consistency) 입니다.
스토리 — 무너지면 안 되는 규칙들
재고가 0인 상품에 주문이 하나 더 들어왔습니다. 코드는 재고를 깎아 -1로 만든 채
그대로 커밋했어요. 원자성은 만족했죠 — 깎는 작업이 온전히 끝났으니까. 그런데 다음 날,
창고 직원은 있지도 않은 물건을 배송 목록에서 발견합니다. “전부 됐지만 틀린”
데이터의 전형이에요.
이런 사고를 막으려면, 데이터에 “이건 항상 참이어야 해”라는 규칙을 걸어야 합니다. 이걸 불변식(invariant) 이라고 불러요.
- 계좌 잔고는 음수가 될 수 없다.
- 재고는 0보다 작아질 수 없다.
- 주문의 총액은 담긴 상품 가격의 합과 같아야 한다.
- 모든 주문에는 존재하는 회원 ID가 붙어야 한다.
일관성의 약속은 이겁니다. 트랜잭션이 시작할 때 이 규칙들이 참이었다면, 끝난
뒤에도 반드시 참이다. 중간에는 잠깐 어긋날 수 있어요(이체 도중엔 A만 줄고 B는
아직 안 늘었으니까). 하지만 COMMIT으로 문을 나서는 순간, 데이터는 다시 “말이
되는 상태”여야 합니다. 어기는 트랜잭션은 아예 통과시키지 않아요.
핵심 — C는 혼자 일하지 않는다
이제 이 책에서 가장 미묘한 이야기를 할게요. ACID의 C는 나머지 세 글자와 결이 다릅니다. A·I·D는 DB가 독립된 메커니즘으로 직접 제공해요(롤백, 잠금, 로그). 하지만 C는 그런 전용 장치가 따로 있는 게 아니라, 여러 힘이 합쳐진 결과에 가깝습니다.
무슨 뜻이냐면 — 일관성은 이렇게 지켜집니다.
- 원자성(A) 이 “규칙 위반이면 통째 롤백”으로 절반의 위반 상태를 막고,
- 격리성(I) 이 동시 실행이 규칙을 깨지 않게 막고,
- 지속성(D) 이 지켜진 상태가 크래시로 사라지지 않게 지키고,
- 거기에 당신이 선언한 규칙과 올바른 애플리케이션 로직이 더해진다.
즉 C는 A·I·D가 지켜주는 토대 위에서, 당신이 선언한 규칙과 올바른 로직이 만들어내는 결과에 가까워요. DB가 도와주는 부분은 명확합니다. 당신이 규칙을 선언해두면, 그걸 어기는 트랜잭션을 DB가 막아줍니다.
CREATE TABLE account (
id VARCHAR(20) PRIMARY KEY,
balance INT NOT NULL CHECK (balance >= 0) -- 잔고 음수 금지 규칙
);
CREATE TABLE orders (
order_no INT PRIMARY KEY,
user_id INT NOT NULL,
FOREIGN KEY (user_id) REFERENCES member(id) -- 존재하는 회원만 (외래키)
);이제 잔고를 음수로 만드는 트랜잭션이 오면?
START TRANSACTION;
UPDATE account SET balance = balance - 100 WHERE id = 'A'; -- ❌ 잔고 50인데 100 출금
-- → 이 문장 실행 순간 CHECK 위반, 문이 거부됨
COMMIT; -- 도달하지 못하거나, 앱/프레임워크가 대신 ROLLBACK을 부른다DB가 CHECK 제약을 어긴 걸 그 문장을 실행하는 순간 잡아내 거부합니다. 규칙을
어긴 상태는 애초에 커밋되지 못해요. 실패한 문은 거부되고, 트랜잭션은 시작 전
상태로 되돌아갑니다(전체 롤백은 앱/프레임워크가 호출). 여기서 A(원자성)가 등판하죠.
보이시나요? C가 A에 기대는 지점이.
참고: MySQL은 8.0.16부터
CHECK를 실제로 강제합니다. 그 전 버전(5.7 등)은CHECK를 선언해도 파싱만 하고 무시했어요. 아직 5.7을 쓴다면 “선언하면 막아준다”가 조건부라는 뜻이니 주의하세요.
하지만 DB가 모든 규칙을 알아서 지켜주진 않습니다. “주문 총액 = 상품 가격의 합”
같은 업무 규칙은 CHECK 하나로 표현하기 어려워요. 이런 건 결국 당신의 코드가
책임집니다. 그래서 일관성은 DB와 개발자의 공동 책임이에요. DB는 선언된 규칙을
지키고, 나머지 “말이 되는가”는 애플리케이션이 지킨다.
정리
- 일관성(Consistency) = 트랜잭션 전후로 데이터가 불변식(규칙) 을 항상 만족. “전부 됐다”(원자성)와 “올바르다”(일관성)는 다른 약속이다.
- C는 A·I·D처럼 독립 메커니즘이 아니다. A+I+D + 당신이 선언한 규칙 + 올바른 로직이 합쳐진 결과에 가깝다.
- DB는 선언된 제약(
NOT NULL,CHECK,FK,UNIQUE…)을 지켜주고, 위반 시 롤백한다. 하지만 “주문 총액 = 합계” 같은 업무 규칙은 개발자 몫 → 일관성은 공동 책임.
생각해볼 질문: 위에서 C가 “격리성(I) 덕분에 동시 실행에도 규칙이 안 깨진다”고 슬쩍 넘어갔죠. 그런데 수천 명이 동시에 같은 데이터를 건드리면, 규칙을 지키는 일이 갑자기 왜 그렇게 어려워질까요? ACID에서 가장 까다로운 글자, I가 기다립니다.