식별 관계와 비식별 관계
게시글 댓글의 순번 충돌을 재현하고 부모 키를 PK에 포함할지 대리 키와 업무 UNIQUE로 분리할지 판단합니다.
식별 관계에서는 부모 키가 자식 PK의 일부가 되어 자식 정체성에 참여합니다.
비식별 관계에서는 부모 키가 일반 FK이고 자식은 별 PK를 가집니다.
실선·점선 모양보다 자식을 어떤 값으로 참조하고 부모 이동·삭제를 어떻게 볼지가 핵심입니다.
10장에서는 후보 키로 행을 구별하고 FK로 테이블 사이 관계를 연결했습니다.
이번 장은 그 두 개념을 합쳐 “자식의 정체성을 부모 키와 함께 말해야 하는가?”를 결정합니다.
부모 키가 자식 PK에 들어가면 식별 관계, 별도 자식 PK를 두고 부모 키를 일반 FK로만 보관하면 비식별 관계입니다.
한 게시글에는 순서 있는 댓글이 여러 개 있고 sequence_no는 게시글 안에서만 유일합니다.
답글이 아직 없어 댓글을 외부에서 독립 참조할 요구도 작습니다.
(post_id, sequence_no) 복합 PK와 comment_id 대리 PK+조합 UNIQUE 두 선택을 비교합니다.
부모 범위가 없는 순번 키
첫 게시글의 1번 댓글 뒤 두 번째 게시글의 1번을 넣으면 정상 업무인데 전역 PK 중복으로 거절됩니다.
sequence_no의 유일 범위가 게시글이라는 요구를 키가 표현하지 못합니다.
DROP TABLE IF EXISTS comment_bad;
SET @post_one := (SELECT MIN(id) FROM posts);
SET @post_two := (SELECT MIN(id) FROM posts WHERE id > @post_one);
CREATE TABLE comment_bad (
sequence_no SMALLINT UNSIGNED NOT NULL,
post_id BIGINT UNSIGNED NOT NULL,
summary VARCHAR(240) NOT NULL,
PRIMARY KEY (sequence_no),
FOREIGN KEY (post_id) REFERENCES posts (id)
);
INSERT INTO comment_bad VALUES (1, @post_one, '인덱스 원리');
INSERT INTO comment_bad VALUES (1, @post_two, '트랜잭션 경계');서로 다른 게시글의 합법적인 1번인데 PK가 부모 범위를 모르므로 두 번째 INSERT에서 오류가 발생합니다.
오류 실행 결과first insert: Query OK
second insert:
ERROR 1062 (23000): Duplicate entry '1' for key 'comment_bad.PRIMARY'
business duplicate: no
schema duplicate: yessequence_no는 부분 식별자입니다.
부모 post_id와 함께일 때만 댓글 하나를 찾을 수 있습니다.
FK를 별 컬럼으로 두었다는 사실만으로 PK의 유일 범위가 자동 확장되지는 않습니다.
전역 comment_id를 추가해도 (post_id, sequence_no) 업무 유일성을 버리면 같은 게시글의 1번을 두 번 저장할 수 있습니다.
대표 ID와 업무 후보 키를 함께 보존합니다.
부분 키의 유일 범위를 반례로 찾기
- 유일 범위 — 같은 순서가 어디까지 반복 가능한지 자연어로 씁니다.
- 최소 키 —
post_id와sequence_no중 하나를 뺐을 때 유일성이 깨지는지 봅니다. - 참조 — 다른 테이블이 댓글을 얼마나 자주 참조할지 예상합니다.
- 이동 — 댓글이 다른 게시글로 옮겨질 수 있는 업무인지 확인합니다.
식별 관계의 의미
복합 PK (post_id, sequence_no)는 ‘이 게시글의 몇 번째 댓글’라는 정체성을 직접 표현합니다.
부모 없이 자식 키를 말할 수 없고 다른 게시글로 이동은 PK 변경입니다.
비식별안은 comment_id로 대표 식별을 고정하고 post_id를 FK로, 조합을 UNIQUE로 둡니다.
참조와 이동은 단순해지지만 업무 유일성을 별 제약으로 관리합니다.
현대적 기본값은 대리 키인 경우가 많아도 종속 자식에는 복합 키가 충분히 합리적입니다.
종속 수명에서 대표 키를 고르기
- 종속 수명 — 부모 삭제와 함께 의미가 사라지는지 봅니다.
- 외부 참조 — 하위 테이블이 복합 FK를 전파받을 비용을 계산합니다.
- 이동 가능 — 부모 변경이 정체성 변경인지 일반 속성 변경인지 정합니다.
- 키 폭 — 부모 PK 타입과 깊이가 인덱스·JOIN에 전파되는지 봅니다.
식별·비식별 모델 비교
단순 댓글는 복합 PK 식별안을 먼저 구현합니다.
이어 독립 답글·공유 참조가 늘어날 경우 대리 키 전환안을 함께 기록합니다.
DROP TABLE IF EXISTS post_comment_surrogate;
DROP TABLE IF EXISTS post_comment_identifying;
CREATE TABLE post_comment_identifying (
post_id BIGINT UNSIGNED NOT NULL,
sequence_no SMALLINT UNSIGNED NOT NULL,
summary VARCHAR(240) NOT NULL,
checked_at DATETIME(6) NULL,
PRIMARY KEY (post_id, sequence_no),
FOREIGN KEY (post_id) REFERENCES posts (id)
ON DELETE CASCADE,
CHECK (sequence_no >= 1)
);
CREATE TABLE post_comment_surrogate (
comment_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
post_id BIGINT UNSIGNED NOT NULL,
sequence_no SMALLINT UNSIGNED NOT NULL,
summary VARCHAR(240) NOT NULL,
PRIMARY KEY (comment_id),
UNIQUE (post_id, sequence_no),
FOREIGN KEY (post_id) REFERENCES posts (id)
);
INSERT INTO post_comment_identifying
(post_id, sequence_no, summary)
VALUES
(@post_one, 1, '인덱스 원리'),
(@post_two, 1, '트랜잭션 경계');
INSERT INTO post_comment_surrogate
(post_id, sequence_no, summary)
VALUES (@post_one, 1, '인덱스 원리');
SET @surrogate_comment_id := LAST_INSERT_ID();
INSERT INTO post_comment_surrogate
(post_id, sequence_no, summary)
VALUES (@post_two, 1, '트랜잭션 경계');두 모델 모두 게시글별 1번을 허용하고 같은 게시글의 중복 순번을 거절합니다.
참조 키 모양만 달라집니다.
INSERT INTO post_comment_identifying
(post_id, sequence_no, summary)
VALUES (@post_one, 1, '중복 댓글');
-- ERROR 1062: PRIMARY 키 중복INSERT INTO post_comment_surrogate
(post_id, sequence_no, summary)
VALUES (@post_one, 1, '중복 댓글');
-- ERROR 1062: (post_id, sequence_no) UNIQUE 중복first post, sequence 1: inserted
second post, sequence 1: inserted
first post, sequence 1 again: ERROR 1062
identifying reference key: (post_id, sequence_no)
surrogate reference key: comment_idCASCADE는 댓글이 게시글에 완전히 종속되고 삭제 감사 요구가 없을 때만 맞습니다.
완료 증거를 보존해야 하면 게시글 소프트 삭제와 RESTRICT를 택합니다.
대리 키 안에서도 조합 UNIQUE는 조회 경로와 업무 검증에 유용합니다.
UNIQUE 컬럼 순서는 게시글별 순번 조회와 맞지만 comment_id 단독 조회를 대체하지는 않습니다.
합법 반복·중복·삭제를 차례로 검증하기
- 합법 반복 — 서로 다른 게시글에 순서 1을 넣습니다.
- 업무 중복 — 같은 조합을 재삽입해 1062를 확인합니다.
- 부모 삭제 — CASCADE·RESTRICT가 요구한 보존 결과인지 확인합니다.
- 하위 참조 — 답글 테이블 FK 컬럼 수를 두 안으로 스케치합니다.
키 최소성과 참조 폭
information_schema에서 PK 컬럼 수와 인덱스 순서를 읽고 대표 조회 SQL을 비교합니다.
SELECT post_id, sequence_no, summary
FROM post_comment_identifying
WHERE post_id = 1 AND sequence_no = 1;
SELECT comment_id, post_id, sequence_no, summary
FROM post_comment_surrogate
WHERE comment_id = @surrogate_comment_id;
SHOW INDEX FROM post_comment_identifying;
SHOW INDEX FROM post_comment_surrogate;식별안 PK는 두 열, 대리안 PK는 한 열이며 조합 UNIQUE가 별도로 나타납니다.
두 조회 모두 정확히 한 행을 반환하도록 제약이 보장합니다.
복합 키가 하위 FK로 내려가는 순간
식별 관계는 강한 소유를 표현하지만 모든 소유 관계를 식별로 만들 필요는 없습니다.
post_attachment처럼 게시글과 함께만 의미 있고 순서로 식별되는 행에는 자연스럽지만, 독립 수명주기와 외부 링크가 생기면 대리 키가 안정적입니다.
부모 PK가 복합이면 자식 PK와 모든 하위 FK에 계속 전파되어 열 수가 깊이마다 늘 수 있습니다.
이 비용은 다음 문서에서 답글·반응 계층으로 재현합니다.
댓글 키 전략 비교
| 선택 | 얻는 효과 | 감수할 비용 | 적합한 조건 |
|---|---|---|---|
| 복합 식별 PK | 업무 정체성 직접 표현 | 하위 FK 폭 | 종속·얕은 구조일 때 |
| 대리 PK+UNIQUE | 짧은 참조·이동 용이 | 제약 두 개 유지 | 외부 참조·확장이 있을 때 |
| CASCADE | 소유 자식 자동 정리 | 감사 소실·대량 삭제 | 완전 종속이며 보존 불필요할 때 |
| RESTRICT | 명시적 수명 제어 | 삭제 절차 증가 | 증거 보존이 중요할 때 |
복합 후보의 최소성을 직접 깨 보기
- 키 제거 — 복합 후보에서 각 컬럼을 빼고 유일성 반례를 만듭니다.
- 참조 DDL — 답글 FK를 식별안과 대리안으로 각각 작성합니다.
- 이동 — 댓글을 다른 게시글로 옮길 때 UPDATE 컬럼과 FK 영향을 비교합니다.
- 삭제 — 부모 삭제 1건이 몇 자식 행을 바꾸는지 트랜잭션으로 측정합니다.
대리 키 전환과 연쇄 규모
복합 PK를 대리 키로 마이그레이션할 때 comment_id 백필, 조합 UNIQUE 유지, 하위 FK 이중 쓰기, 전환 일치 검사를 단계화합니다.
대량 CASCADE는 한 트랜잭션의 잠금·언두 로그를 키울 수 있으므로 보존 정책과 분할 단위 삭제를 검토합니다.
재정렬·테넌트·동시 삽입 예외
- 순서 재정렬은 식별 PK 변경이 되어 하위 FK까지 ON UPDATE 비용을 만들 수 있습니다.
- 동시 중간 삽입은 조합 UNIQUE 충돌과 재번호 잠금이 필요합니다.
- 부모
tenant_id까지 복합 키에 넣으면 보안 범위 이득과 전파 폭을 함께 봅니다. AUTO_INCREMENT대리 키가 있어도 같은 게시글 순서 중복은 자동으로 막히지 않습니다.
다음 문서는 식별 키가 답글·반응 단계로 전파될 때 쿼리와 인덱스가 얼마나 넓어지는지 측정하고 비식별 전환 기준을 세웁니다.
식별 관계 선택 기준
| 판단 축 | 확인할 질문 |
|---|---|
| 종속 | 자식이 부모 없이 의미·수명을 가질 수 없는가? |
| 부분 키 | 부모 안에서만 유일한 최소 식별자가 있는가? |
| 참조 깊이 | 하위 FK가 몇 단계까지 키를 전달받는가? |
| 이동 | 부모 변경이 허용되는 일반 업무인가? |
| 보존 | 부모 삭제와 자식 증거 삭제가 같은 정책인가? |
ERD 선 모양을 취향으로 고르지 않고 자식의 언어적 식별, 외부 참조, 이동, 삭제를 예제 데이터로 확인합니다.
대리 키 선택은 업무 UNIQUE 제거가 아닙니다.
연습 문제
게시글 안에서 1번부터 시작하는 질문 목록을 설계하세요.
질문 답변과 답글이 질문을 참조합니다.
식별·비식별안을 비교하고 하나를 선택하세요.
해설과 예시 답안
답변·답글까지 하위 참조가 이어지므로 question_id 대리 키와 (post_id, sequence_no) UNIQUE를 권합니다.
게시글별 순번 규칙은 유지하면서 하위 FK를 한 열로 제한합니다.
CREATE TABLE post_question (
question_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
post_id BIGINT UNSIGNED NOT NULL,
sequence_no SMALLINT UNSIGNED NOT NULL,
body VARCHAR(500) NOT NULL,
PRIMARY KEY (question_id),
UNIQUE (post_id, sequence_no),
FOREIGN KEY (post_id) REFERENCES posts (id)
);서로 다른 게시글 1번은 허용되고 같은 조합은 거절되는지, 답변 FK가 question_id 하나만 가지는지 확인합니다.
핵심 정리
- 식별 관계는 부모 키가 자식 PK에 참여합니다.
- 부분 식별자는 부모 범위와 함께 후보 키가 됩니다.
- 대리 PK를 써도 업무 조합 UNIQUE는 보존합니다.
- 참조 깊이·이동·삭제 정책이 식별 여부를 결정합니다.
다음 문서에서는 복합 키 전파가 쿼리·인덱스·변경 비용에 미치는 영향을 계층으로 확인합니다.