테마
외래키와 잠금 대기
FK(Foreign Key, 외래 키)는 참조 무결성(referential integrity)을 지키는 제약이다. 자식 테이블의 칼럼이 부모 테이블에 없는 값을 갖지 못하게 DB가 막는다. 예를 들어 아래 orders.user_id에는 users에 실제로 있는 id만 들어갈 수 있다. 면접 포인트는 제약 자체보다 부모·자식 양쪽에서 생기는 양방향 잠금 대기와 FK 칼럼의 인덱스 필수다.
기본 동작
sql
CREATE TABLE orders (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT NOT NULL,
FOREIGN KEY (user_id) REFERENCES users (id)
ON DELETE RESTRICT
ON UPDATE CASCADE
);- 자식 행을 INSERT·UPDATE할 때 부모 테이블에 그 키가 있는지 확인한다. 없으면 FK 제약 위반을 뜻하는 에러 1452로 거부한다.
- 부모 행을 DELETE·UPDATE할 때 그 행을 참조하는 자식을 어떻게 할지는 옵션으로 정한다.
RESTRICT는 부모 변경을 막고,CASCADE는 자식도 연쇄로 함께 지우거나 바꾸며,SET NULL은 자식의 FK 칼럼을 NULL로 바꾼다.NO ACTION은 표준 SQL 이름이고 InnoDB에서는RESTRICT와 같다. - 자식 FK 칼럼에 인덱스가 없으면 자동 생성된다. 부모 행을 UPDATE·DELETE할 때 그 부모를 참조하는 자식 행을 풀 스캔 없이 바로 찾아야 하고, 그래야 검사·잠금 범위도 좁아지기 때문이다. 자식을 넣을 때 부모 값이 있는지 확인하는 쪽은 부모의 PK(Primary Key, 기본 키)·유니크 인덱스(unique index)를 쓴다.
양방향 대기 — 두 장면
1. 자식 변경이 대기한다. 자식을 INSERT·UPDATE하면 InnoDB는 부모 행에 공유 락(shared lock)을 걸고 존재를 확인한다. 공유 락은 그것을 건 트랜잭션(transaction)이 끝날 때까지 다른 쪽이 그 행을 고치지 못하게 하는 읽기용 잠금이다. 그런데 다른 트랜잭션이 그 부모 행을 바꾸는 중이라 쓰기용 잠금인 배타 락(exclusive lock, X 락)을 쥐고 있으면, 자식 쪽은 그 트랜잭션이 끝날 때까지 대기한다.
2. 부모 변경이 대기한다. 부모를 DELETE·UPDATE하면 자식 테이블에 그 부모를 참조하는 행이 있는지 검사하고, 검사하면서 살펴본 자식 행에도 잠금을 건다. 그래서 다른 트랜잭션이 그 자식 행을 잡고 있으면 부모 쪽이 기다린다. InnoDB는 FK 칼럼이 맨 앞에 오는 인덱스를 요구하고 없으면 자동으로 만들므로, 인덱스 없이 자식 테이블을 뒤지는 FK 검사는 정상 상태가 아니다. 그래도 한 부모를 참조하는 자식이 매우 많거나 CASCADE로 함께 바뀌는 범위가 크면, 검사할 행과 걸 잠금이 늘어 비용이 커진다.
다이어그램 로딩…
실무 논쟁: FK를 걸 것인가
- 찬성: DB가 무결성을 보장한다. 애플리케이션에 버그가 있어도 부모 없는 자식 행, 즉 고아 행(orphan row)이 생기지 않는다.
- 반대: 쓰기마다 부모 확인과 잠금 비용이 붙는다. 데이터를 여러 DB 서버에 나눠 담는 샤딩(sharding)이나 대용량 데이터를 다른 곳으로 옮기는 마이그레이션(migration)에서도 걸림돌이 된다. 그래서 FK 없이 애플리케이션에서 무결성을 보장하는 조직도 많다.
- 면접 답은 "트레이드오프 + 팀 규칙"이다. 거는 쪽이면 인덱스·옵션(RESTRICT/CASCADE)을 정확히, 안 거는 쪽이면 고아 행 검증을 어떻게 할지까지 말해야 한다.
면접 포인트
- "FK 자식 칼럼에 인덱스가 왜 필요해요?" → 부모 변경 시 참조하는 자식 행을 풀 스캔 없이 찾고 검사·잠금 범위를 줄이기 위해(존재 확인은 부모 인덱스 몫).
- "부모 DELETE가 느려요" → 자식 참조 검사 범위·잠금 확인. 참조량이 많거나 인덱스 순서가 안 맞으면 느려진다.
- "CASCADE의 위험?" → 연쇄 삭제 범위 예측 불가 + 대량 잠금. 논리 삭제(soft delete, 삭제 표시만 하기)·배치 정리와 비교.
