회사 테이블을 찾아보다 보니 외래키가 하나도 설정되지 않은 것을 보았다. 이렇게 외래키를 실무에서 쓰지 않는 이유는 뭘까?
그것에 대해 답변이 달려 있는 것을 보게 되었다.
외래키를 실무에서 잘 사용하지 않는 이유와 대안
실무에서 외래키를 쓰지 않는 이유 - 어느 강의의 답변
외래키를 사용하는 이유는 데이터의 정합성을 유지하기 위해서 사용을 하는데요.
하지만 실무에서는 수작업으로 데이터를 다루는(수정, 생성) 경우가 빈번합니다. 이때 테이블의 관계상 데이터 생성 순서가 맞지 않으면 에러가 발생하기도 하고, 어쩔 수 없이(e.x, 데이터 재생성을 위한 등) 데이터(자식 테이블)를 삭제 하는 경우 CASCADE 옵션이 걸려 있다면 부모 테이블 데이터가 삭제가 되는 참사가 발생하기도 합니다.
문제가 생겨 빠르게 수작업을 처리를 해야 되는 경우에 외래키로 인해서 처리가 늦어질 수도 있고, 작업을 처리하는 개발자의 번거로움이 있습니다.
외래키가 걸려 있는 테이블을 다룰 때 수작업 시 번거로움으로 인해서 외래키를 사용하지 않는 경우가 많습니다 :)
또 다른 이유로는 데이터베이스 설계를 초기에 잘 해놓았더라도 시간이 지나면서 추가 개발과 설계가 수정이 되어 질 수 있습니다. 데이터 정합성을 유지하기 위한 외래키가 이후에는 더 큰 수정 개발을 불러 일으킬 수도 있겠습니다. 외래키를 만들 때는 득과 실을 잘 따져보고 걸어야 되지만 제 경우에는 실제 실무(Spring boot 및 오라클, MY-SQL 기반)에서는 아직까지는 외래키를 생성하는 경우를 본 적이 없습니다.
1. 외래키(Foreign Key)의 장점
- 데이터 무결성: 데이터베이스가 참조 무결성을 자동으로 보장하여 잘못된 데이터 삽입 및 삭제를 방지할 수 있다.
- 데이터 정합성: 부모-자식 관계를 유지하며 논리적으로 잘못된 데이터를 방지할 수 있다.
하지만 실무에서는 외래키 제약조건이 사용되지 않는 경우가 많다. 그 이유는 다음과 같은 오버헤드와 불편함 때문이다.
2. 외래키가 발생시키는 오버헤드
- 추가적인 쿼리 발생
- INSERT: 자식 테이블에 데이터를 삽입할 때, 부모 테이블의 참조 키 존재 여부를 확인하는 추가 SELECT 쿼리가 실행된다.
- UPDATE: 자식 테이블의 값이 변경되면 부모 테이블에서 참조 키를 확인하거나, 참조 무결성을 위해 추가 검증 작업이 발생한다.
- DELETE: 부모 테이블 데이터를 삭제할 때 자식 테이블에서 참조 중인 데이터가 있는지 확인하며, CASCADE 옵션이 설정된 경우 자식 데이터를 삭제할 수 있다.
이러한 검증 과정은 쿼리 실행 시간이 증가하고, 동시에 데이터베이스에 락(Lock)이 발생해 성능 저하를 유발할 수 있다. 이는 데드락(Deadlock)을 초래할 수 있다.
- 테스트 코드 작성의 불편함
- 연관 관계가 설정된 모든 테이블에 데이터를 삽입해야 테스트가 정상적으로 동작할 수 있다.
- 아래와 같은 복잡한 코드 작성이 필요하다:
@BeforeEach
public void init() {
RequestScheduleWithUserDto requestDto = new RequestScheduleWithUserDto();
requestDto.setUsername("user1");
requestDto.setPassword("1234");
requestDto.setTodoList("hi");
requestDto.setEmail("user1@naver.com");
UUID scheduleId = UUID.randomUUID();
UUID userId = UUID.randomUUID();
// USER 테이블과 SCHEDULE 테이블에 데이터를 삽입
userRepository.add(scheduleId, userId, LocalDateTime.now(), requestDto);
scheduleRepository.add(scheduleId, userId, LocalDateTime.now(), requestDto);
}
- 운영 환경에서의 불편함
- 데이터 재적재 시 종속 관계에 따라 외래키를 일시적으로 비활성화해야 할 수 있다.
- FK를 활성화하는 과정에서 대량 데이터 검증이 필요해 시간이 소요될 수 있다.
- 데이터베이스 확장성 문제
- 외래키가 있으면 테이블 구조 변경, 데이터 마이그레이션 등이 어려워질 수 있다.
- 이는 시간과 비용의 증가를 유발할 수 있다.
3. 데드락(Deadlock)이란?
- 정의: 두 개 이상의 트랜잭션이 서로 자원을 점유한 상태에서, 상대방이 점유하고 있는 자원을 요청하며 무한정 대기하는 상황을 말한다.
- 발생 원인:
- 락(Lock) 경합: 트랜잭션이 동일한 자원에 대해 서로 다른 락(읽기/쓰기)을 요청하면서 충돌할 수 있다.
- 순환 대기: 트랜잭션 A가 자원 X를 점유하고 자원 Y를 요청하고, 트랜잭션 B가 자원 Y를 점유하고 자원 X를 요청하는 상황이 발생할 수 있다.
- 비선점성: 이미 점유한 자원을 다른 트랜잭션이 강제로 가져갈 수 없는 경우다.
- 예시:
- 트랜잭션 A는
schedule
락을 요청하며 대기 중이고, 트랜잭션 B는user
락을 요청하며 대기 중인 상태에서 데드락이 발생할 수 있다.
- 트랜잭션 A는
- 해결 방안:
- 트랜잭션 순서 보장: 모든 트랜잭션이 동일한 순서로 자원을 요청하도록 설계할 수 있다.
- 락 범위 최소화: 트랜잭션에서 가능한 최소한의 자원만 락을 점유할 수 있다.
- 타임아웃 설정: 일정 시간이 지나면 트랜잭션을 강제로 종료할 수 있다.
트랜잭션 A:
BEGIN;
LOCK TABLE user;
LOCK TABLE schedule;
트랜잭션 B:
BEGIN;
LOCK TABLE schedule;
LOCK TABLE user;
4. 실무에서 외래키를 대체하는 방법
1) JOIN 활용
외래키 없이도 JOIN
을 사용해 관계형 데이터를 처리할 수 있다.
SELECT u.username, s.todo_list
FROM user u
JOIN schedule s ON u.user_id = s.user_id
WHERE u.email = 'user1@naver.com';
2) UNIQUE 및 INDEX 사용
부모 테이블의 참조 키에 대해 UNIQUE 또는 INDEX를 설정해 데이터 중복을 방지하고 빠른 조회를 지원할 수 있다.
SQL 예시:
ALTER TABLE user ADD UNIQUE (user_id);
CREATE INDEX idx_user_id ON schedule(user_id);
3) 애플리케이션 레벨에서 무결성 관리
데이터 무결성을 데이터베이스가 아닌 애플리케이션 코드에서 관리할 수 있다.
if (!userRepository.existsById(userId)) {
throw new IllegalArgumentException("Invalid User ID");
}
scheduleRepository.add(scheduleId, userId, LocalDateTime.now(), requestDto);
4) 트리거(Trigger) 사용
데이터베이스 트리거로 무결성 조건을 강제할 수 있다.
CREATE TRIGGER check_user_exists
BEFORE INSERT ON schedule
FOR EACH ROW
BEGIN
IF NOT EXISTS (SELECT 1 FROM user WHERE user_id = NEW.user_id) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'User ID does not exist';
END IF;
END;
5. 외래키 사용이 적합한 경우
- 결제 및 재무 데이터: 데이터 정합성과 무결성이 중요할 때 FK를 사용할 수 있다.
- 작은 규모의 애플리케이션: 데이터 변경이 적고 성능 요구 사항이 낮을 때 FK를 사용하는 것이 편리하다.
🏷️ 정리
- 그렇기 때문에 실제 실무에서 mybatis 사용 코드를 보면 join, 서브쿼리가 10개 이상씩 써 있는 것들도 많다.
- index 등을 잘 이용하면 잘 이용하면 좋다
- 외래키는 무결성을 보장하지만 성능 및 운영상의 오버헤드를 초래할 수 있다.
- 실무에서는 JOIN, UNIQUE, INDEX, 애플리케이션 코드 등을 활용해 무결성을 관리할 수 있다.
- 외래키 사용 여부는 성능, 데이터 정합성, 유지보수성을 종합적으로 고려하여 결정해야 한다.
'데이터베이스 > SQL 지식' 카테고리의 다른 글
나는 select만 했을 뿐인데.. 조회도 방심 금물, 트랜잭션이 묶이는 이유 (0) | 2025.01.16 |
---|
안녕하세요. si 회사 소속 sm LMS 팀에 소속중인 1년차 백엔드 개발자입니다😀 함께 나누고 성장하는 것을 좋아해요. 언제든 디스코드나 구글 메일로 질문해도 됩니다!
⭐ 잘못된 내용은 댓글 적어주세요 :)