최근 개발 관련 오픈 톡방에서 실무에서 외래 키(Foreign Key)를 사용하지 않는 이유에 대한 질문이 올라왔고, 다양한 개발자들의 의견을 접할 수 있었습니다.
저 역시 개인 프로젝트를 진행하면서 테이블 간의 관계를 명확히 맺는 것이 '좋은 설계'라고 생각해왔기 때문에, 이 주제에 호기심이 생겼고 공부해보게 되었습니다.
토이 프로젝트를 할 때는 JPA의 @ManyToOne, @OneToMany, @JoinColumn 등을 적극적으로 활용하며 테이블 간의 관계를 세밀하게 설계했습니다.
외래 키를 사용하는 것은 다음과 같은 명확한 장점이 있습니다.
fetch join 등)하지만 실무에서는 생각보다 FK를 사용하지 않는 경우가 많고, 오히려 FK 없이 유연하게 관리하는 구조가 더 일반적이라는 사실을 알게 되었습니다. 왜 그럴까요?
FK는 테이블 간의 강한 결합을 의미합니다. 한 테이블의 변경이나 삭제가 다른 테이블에 직접적인 영향을 미치기 때문에, 스키마 변경 시 높은 리스크를 동반합니다.
서비스가 성장할수록 테이블은 많아지고 연결 관계도 복잡해지기 때문에, 작은 수정이 여러 테이블로 전파될 가능성이 커집니다.
또한 DB 마이그레이션 시 FK가 존재하면 배포 순서에까지 영향을 줄 수 있어 유지보수와 운영이 어려워집니다.
FK 제약 조건으로 인해 참조 무결성 오류가 발생하면 전체 트랜잭션이 실패합니다.
특히 대용량 트래픽 환경에서는 이 작은 오류가 전체 서비스 장애로 이어질 수 있어, 실무에서는 이 같은 상황을 적극적으로 피하려고 합니다.
FK는 무결성 보장을 위해 내부적으로 락을 걸기 때문에, 트랜잭션 간에 데드락(Deadlock)이 발생할 수 있습니다.
예를 들어 하나의 트랜잭션이 부모 테이블을 업데이트하고, 동시에 다른 트랜잭션이 자식 테이블을 수정하면, FK로 인해 양쪽이 서로를 기다리다 데드락이 발생할 수 있습니다.
대규모 트래픽 환경에서는 이 문제가 빈번하게 발생하며, 서비스 장애로 이어질 수 있습니다.
실무에서는 FK를 제거하더라도, 다음과 같은 방식으로 데이터의 정합성과 무결성을 확보합니다.
애플리케이션 단에서 참조 대상 존재 여부를 검증
예: userId를 참조할 때, 먼저 해당 유저가 존재하는지 조회 후 진행
@Service
@RequiredArgsConstructor
public class CommentService {
private final PostRepository postRepository;
private final CommentRepository commentRepository;
public void createComment(Long postId, CommentRequest request) {
// FK 없이 게시글 존재 여부 확인
if (!postRepository.existsById(postId)) {
throw new IllegalArgumentException("존재하지 않는 게시글입니다.");
}
// ... 중략
commentRepository.save(comment);
}
}
JOIN
물리적인 FK가 없더라도, 쿼리에서 필요한 경우 JOIN을 통해 연관된 데이터를 조회함
SELECT c.id, c.author, c.content
FROM comments c
JOIN posts p ON c.post_id = p.id
WHERE p.id = 1;
3. UNIQUE 및 INDEX 사용으로 성능 확보
3-1. 게시글 제목에 UNIQUE 제약 걸기
ALTER TABLE posts
ADD CONSTRAINT uk_post_title UNIQUE (title);
```
3-2. 댓글의 post_id에 인덱스 추가 (조회 성능 향상)
CREATE INDEX idx_comment_post_id ON comments(post_id);
```
FK를 실무에서 항상 피해야 하는 것은 아닙니다. 다음과 같은 상황에서는 FK를 사용하는 것이 적합할 수 있습니다.
이처럼 상황에 따라 FK를 사용하는 것이 더 효율적일 수도 있기 때문에, 중요한 것은 현재 시스템의 특성과 우선순위에 맞는 선택입니다.
처음에는 FK를 안 쓴다는 얘기가 낯설고 이상하게 느껴졌지만,
조금씩 실무의 요구 사항과 문제 상황을 접하다 보니,
FK 없이 더 유연하게, 더 안정적으로 서비스 운영이 가능한 이유를 이해할 수 있게 되었습니다.
FK는 강력한 도구이지만, 그만큼 제약과 위험도 함께 존재합니다.
그래서 실무에서는 상황에 따라 FK를 제거하고, 그 기능을 애플리케이션 레벨에서 명시적으로 제어하는 설계를 선택하는 것입니다.