제약 조건 오류 진단
NOT NULL·UNIQUE·FK·검사 오류를 제약 조건과 입력 책임으로 분류하고 올바른 값·순서로 복구합니다.
제약 오류는 DB가 귀찮게 막는 예외가 아니라 데이터 규칙이 작동한 증거입니다.
모든 오류를 ‘저장 오류’ 하나로 묶으면 사용자 입력, 중복 요청, 부모 순서, 상태 전이 중 무엇을 고쳐야 하는지 알 수 없습니다.
오류 번호와 제약 조건 이름을 업무 원인에 연결합니다.
회원과 posts에 있는 NOT NULL, 이메일 UNIQUE, 작성자 FK, 본문·상태 검사를 차례로 위반합니다.
오류 SQL은 데이터를 남기지 않으며, 각 원인을 고친 하나의 정상 게시글으로 마지막 일치 검사를 수행합니다.
제약 오류의 잘못된 처리
필수 이메일 없음, 중복 이메일, 없는 회원, 공백 본문을 같은 일시 장애처럼 다시 실행합니다.
입력을 바꾸지 않은 재시도는 같은 영구 오류를 반복하고, UNIQUE 충돌을 무조건 성공으로 무시하면 다른 사용자의 행을 자기 요청 결과로 오인할 수 있습니다.
-- NOT NULL / required field
INSERT INTO members (email, password_hash, name)
VALUES (NULL, '{demo}hash', '필수값 누락');
-- UNIQUE / duplicate business key
INSERT INTO members (email, password_hash, name)
VALUES ('min@example.com', '{demo}other-hash', '중복 사용자');
-- FOREIGN KEY / missing parent
INSERT INTO posts (
author_id, title, content, status, created_at, updated_at, request_key
) VALUES (
999999, '고아 게시글', '존재하지 않는 작성자를 참조합니다.', 'DRAFT',
'2026-07-07 00:00:00', '2026-07-07 00:00:00', 'orphan-001'
);
-- CHECK / value outside domain
INSERT INTO posts (
author_id, title, content, status, view_count,
created_at, updated_at, request_key
) VALUES (
1, '본문 오류', ' ', 'DRAFT',
0, '2026-07-07 01:00:00', '2026-07-07 01:00:00', 'invalid-content-001'
);각 구문은 서로 다른 규칙에서 오류가 발생합니다.
다음 문장을 클라이언트가 계속 실행했는지와 트랜잭션 상태는 실행 방식에 따라 확인합니다.
오류 실행 결과1048 | Column 'email' cannot be null
1062 | Duplicate entry 'min@example.com' for key 'uq_members_email'
1452 | Cannot add or update a child row: fk_posts_author
3819 | Check constraint 'ck_posts_content' is violated1048은 필수 입력, 1062는 후보 키 중복, 1452는 부모 존재·쓰기 순서, 3819는 도메인 범위 문제입니다.
교착 상태·시간 초과 같은 일시 오류와 달리 같은 입력의 재시도로 해결되지 않습니다.
오류 번호만 저장하지 않고 제약 조건 이름, 요청 키, 안전한 입력 요약을 로그에 남기며 개인정보 원문은 제한합니다.
제약 조건별 무결성
NOT NULL은 값 존재, UNIQUE는 키 중복, FK는 관계 존재, 검사는 값 범위와 행 조합을 지킵니다.
하나의 오류를 없애려고 제약을 끄지 않고 데이터 생성 순서와 입력을 고칩니다.
부모를 먼저 만들고 자식을 넣으며, 중복 요청이면 기존 행이 같은 업무 요청인지 멱등성 키로 확인합니다.
DB 오류는 애플리케이션의 안정적인 도메인 오류로 변환합니다.
제약 개념은 표준이지만 MySQL 오류 번호·메시지 형식은 제품 규칙입니다.
애플리케이션은 메시지 문자열 파싱보다 공급자 코드·SQLSTATE·제약 조건 메타데이터를 이용하고 버전 업그레이드에서 변화를 검증합니다.
INSERT IGNORE나 ON DUPLICATE KEY UPDATE는 오류를 없애는 만능 도구가 아니라 명시적인 충돌 정책이 있을 때 선택합니다.
부모와 자식의 저장 순서
기존 회원을 이메일로 찾고 허용 범위와 상태 조합을 만족하는 게시글을 저장합니다.
중복 회원을 새로 만들지 않고 현재 행을 참조하며 오류를 숨기는 IGNORE는 사용하지 않습니다.
SELECT id, email, status
FROM members
WHERE email = 'min@example.com'
AND status = 'ACTIVE';
INSERT INTO posts (
author_id, title, content, status, view_count,
created_at, updated_at, published_at, request_key
) VALUES (
(SELECT id FROM members WHERE email = 'min@example.com'),
'제약 조건 복습', '제약 조건이 지키는 범위를 게시글로 정리합니다.',
'PUBLISHED', 50,
'2026-07-07 02:00:00', '2026-07-07 02:50:00',
'2026-07-07 02:50:00', 'post-min-constraint-001'
);
SELECT id, author_id, title, view_count, status, version
FROM posts
WHERE title = '제약 조건 복습';부모가 한 행 존재하고 필수·범위·상태 조합을 모두 만족해 게시글 한 건이 저장됩니다.
개선 실행 결과parent rows | 1
inserted rows | 1
view_count | 50
status/time | PUBLISHED + non-NULL published_at
constraint errors | 0SELECT 후 INSERT 사이 부모 상태가 바뀔 수 있으므로 애플리케이션 트랜잭션과 권한 검사를 함께 설계합니다.
FK는 부모 존재만 보장하고 활성 상태까지 강제하지 않습니다.
활성 사용자만 기록할 수 있다는 규칙은 서비스 트랜잭션 또는 더 복잡한 모델이 책임집니다.
DB 제약이 보장하는 것과 보장하지 않는 것을 오류 처리에서 구분합니다.
제약 위반 검사
정상 삽입 뒤 현재 데이터에서 고아, 범위, 상태 조합, 이메일 중복이 없는지 진단합니다.
제약이 이미 막더라도 마이그레이션·검사 우회·복구 데이터를 확인하는 기준 쿼리로 남깁니다.
SELECT
(SELECT COUNT(*)
FROM members
GROUP BY email
HAVING COUNT(*) > 1
LIMIT 1) AS duplicate_email_group,
(SELECT COUNT(*)
FROM posts AS p
LEFT JOIN members AS m ON m.id = p.author_id
WHERE m.id IS NULL) AS orphan_posts,
(SELECT COUNT(*) FROM posts
WHERE CHAR_LENGTH(TRIM(title)) NOT BETWEEN 1 AND 80
OR CHAR_LENGTH(TRIM(content)) NOT BETWEEN 1 AND 5000) AS range_violations,
(SELECT COUNT(*) FROM posts
WHERE (status = 'PUBLISHED' AND published_at IS NULL)
OR (status = 'DRAFT' AND published_at IS NOT NULL))
AS state_pair_violations;duplicate_email_group은 행이 없으면 스칼라 NULL일 수 있고 나머지 세 값은 0이어야 합니다.
진단 쿼리 자체의 NULL 의미도 결과 규칙에 적습니다.
DB 제약과 애플리케이션 검증은 중복이 아니라 다른 시간과 사용자 경험을 담당합니다.
애플리케이션은 빠르고 친절한 오류를 주지만 경쟁 요청 사이의 UNIQUE는 DB만 최종 판단할 수 있습니다.
검사 오류를 HTTP 400, UNIQUE 충돌을 409처럼 변환할 수 있지만 모든 중복이 같은 도메인 의미는 아닙니다.
내부 버그와 사용자 수정 가능 입력을 분리합니다.
네 오류를 한 구문씩 실행하고 오류 번호·SQLSTATE·제약 조건 이름을 표에 기록하세요.
부모를 먼저 추가하면 1452가 사라지는지, 공백 본문을 실제 문장으로 바꾸면 3819가 사라지는지 확인합니다.
중복 이메일은 이름을 바꿔도 계속 중복임을 보고 키가 어느 열인지 설명합니다.
PUBLISHED 상태에 게시 시각을 넣은 뒤 다른 검사가 연쇄적으로 나타나는지도 순서대로 해결합니다.
오류율 지표는 제약 조건별 범위 제한 태그로 집계하고 이메일·id 같은 고유값을 지표 표시명으로 넣지 않습니다.
갑자기 1452가 늘면 배포 순서·이벤트 지연·잘못된 스키마 연결을 조사하고, 1062가 늘면 재시도·멱등성 문제를 봅니다.
제약을 긴급히 비활성화하기 전에 쓰기 중단·원인 수정·격리 테이블·복구 계획을 검토합니다.
UNIQUE는 NULL 허용 열에서 여러 NULL을 허용할 수 있고 정렬 규칙에 따라 대소문자·악센트가 같은 값으로 비교될 수 있습니다.
FK는 같은 트랜잭션 안에서 부모를 먼저 넣으면 커밋 전에도 참조할 수 있지만 MySQL은 일반적으로 지연된 제약 조건을 제공하지 않습니다.
검사의 NULL 알 수 없음 통과, 다중-행 구문 한 행 오류, 트리거가 만든 추가 오류도 구분합니다.
이 오류 분류는 ch5 FK 마이그레이션, ch8 트랜잭션 재시도, ch13 API 오류 규칙에 그대로 이어집니다.
제약 조건 이름을 안정적인 스키마 규칙으로 관리하면 애플리케이션과 운영 대시보드가 같은 원인을 말할 수 있습니다.
뒤의 가져오기 장에서는 거부된 행을 입력 배치 ID와 함께 격리해 수정 후 재처리합니다.
제약 오류 대응 기준
| 판단 축 | 확인할 질문 |
|---|---|
| 종류 | NOT NULL·UNIQUE·FK·검사 중 어느 축인가? |
| 지속성 | 같은 입력의 재시도로 해결되는 일시 오류인가? |
| 책임 | 사용자 입력·쓰기 순서·애플리케이션 버그 중 누가 고치는가? |
| 노출 | 클라이언트에는 안정적인 도메인 오류, 로그에는 제약 조건 증거가 남는가? |
| 복구 | 제약을 끄지 않고 올바른 데이터·순서로 재처리하는가? |
오류 메시지를 지우고 재시도하지 않습니다.
제약 조건 이름과 위반 키를 찾아 수정 가능한 입력인지 시스템 결함인지 분류한 뒤 각기 다른 후속 행동을 선택합니다.
연습 문제
아래 의도를 만족하는 게시글을 스스로 넣으세요: ch2-2 본문에서 저장한 sora@example.com, 조회수 50회, PUBLISHED 상태, 고유한 request_key.
먼저 published_at을 빼서 오류를 확인한 뒤 올바르게 수정하세요.
해설과 예시 답안
부모는 이메일 UNIQUE 서브쿼리로 한 ID를 얻고 PUBLISHED 상태에는 NULL이 아닌 published_at을 함께 제공합니다.
-- 실패: ck_posts_publication_pair
INSERT INTO posts (
author_id, title, content, status, view_count,
created_at, updated_at, published_at, request_key
) VALUES (
(SELECT id FROM members WHERE email = 'sora@example.com'),
'제약 진단 연습', '게시 시각 조합을 확인합니다.', 'PUBLISHED', 50,
'2026-07-08 01:00:00', '2026-07-08 01:00:00', NULL, 'post-sora-check-001'
);
-- 개선
INSERT INTO posts (
author_id, title, content, status, view_count,
created_at, updated_at, published_at, request_key
) VALUES (
(SELECT id FROM members WHERE email = 'sora@example.com'),
'제약 진단 연습', '게시 시각 조합을 확인합니다.', 'PUBLISHED', 50,
'2026-07-08 01:00:00', '2026-07-08 01:50:00',
'2026-07-08 01:50:00', 'post-sora-check-001'
);첫 문장은 3819와 ck_posts_publication_pair 이름으로 오류가 발생하고 둘째만 저장되는지, 같은 제목이 UNIQUE가 아니므로 다른 게시글 제목과 겹칠 수 있음을 확인합니다.
핵심 정리
- NOT NULL·UNIQUE·FK·검사는 서로 다른 무결성 질문을 지킵니다.
- 영구 입력 오류를 같은 입력으로 재시도하거나 제약을 끄지 않습니다.
- DB 오류를 제약 조건 증거와 안정적인 도메인 오류로 연결합니다.
- 애플리케이션 검증과 DB 제약은 사용자 경험과 경쟁 안전을 나누어 맡습니다.
다음 문서는 이미 값이 있는 테이블에 새 필수 열을 단계적으로 추가하는 운영 심화입니다.
기본 DML만 학습한다면 심화를 건너뛰고 3장으로 이동해도 됩니다.