실무에서 외래키(FK)를 안쓰는 이유

오형상·2025년 3월 27일

최근 개발 관련 오픈 톡방에서 실무에서 외래 키(Foreign Key)를 사용하지 않는 이유에 대한 질문이 올라왔고, 다양한 개발자들의 의견을 접할 수 있었습니다.
저 역시 개인 프로젝트를 진행하면서 테이블 간의 관계를 명확히 맺는 것이 '좋은 설계'라고 생각해왔기 때문에, 이 주제에 호기심이 생겼고 공부해보게 되었습니다.

토이 프로젝트를 할 때는 JPA의 @ManyToOne, @OneToMany, @JoinColumn 등을 적극적으로 활용하며 테이블 간의 관계를 세밀하게 설계했습니다.
외래 키를 사용하는 것은 다음과 같은 명확한 장점이 있습니다.

  • 데이터 무결성을 데이터베이스 차원에서 보장
  • 연관된 데이터를 자동으로 조회 가능 (fetch join 등)
  • ERD에서 구조를 한눈에 파악하기 쉬움

하지만 실무에서는 생각보다 FK를 사용하지 않는 경우가 많고, 오히려 FK 없이 유연하게 관리하는 구조가 더 일반적이라는 사실을 알게 되었습니다. 왜 그럴까요?


실무에서 외래 키를 사용하지 않는 이유

스키마 변경과 배포 유연성 저하

FK는 테이블 간의 강한 결합을 의미합니다. 한 테이블의 변경이나 삭제가 다른 테이블에 직접적인 영향을 미치기 때문에, 스키마 변경 시 높은 리스크를 동반합니다.
서비스가 성장할수록 테이블은 많아지고 연결 관계도 복잡해지기 때문에, 작은 수정이 여러 테이블로 전파될 가능성이 커집니다.
또한 DB 마이그레이션 시 FK가 존재하면 배포 순서에까지 영향을 줄 수 있어 유지보수와 운영이 어려워집니다.

장애 전파 위험

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는 강력한 도구이지만, 그만큼 제약과 위험도 함께 존재합니다.
그래서 실무에서는 상황에 따라 FK를 제거하고, 그 기능을 애플리케이션 레벨에서 명시적으로 제어하는 설계를 선택하는 것입니다.

0개의 댓글