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가 기다립니다.