Foreign Key 없이 구축하는 관계형 데이터베이스 시스템에 대한 생각

Updated:

Foreign Key (Referential Integrity) 없는 관계형 데이터베이스라니..

프로젝트를 여기저기 돌아 다니다 보면 여전히 Foreign Key (Referential Integrity) 가 없는 데이터베이스들이 많이 있는걸 본다. Foreign Key (Referential Integrity) 는 Relational Database 의 핵심요소인데도 불구하고 데이터베이스 시스템 개발에서 여전히 소외되는 경향이 강한 것 같다. 그렇다면 Foreign Key (Referential Integrity) 는 왜 환영받지 못하는 것일까. 환영받지 못할만한 이유들에 대한 해결책은 없을까. 이런 생각들을 정리해 보았다.

1. 우선 성능이 느려진다는 이유이다.

과거 Oracle 8i 버전까지는 Foreign Key 관련한 테이블 락(Lock) 경합을 회피하기 위해 Foreign Key 열에 인덱스를 만들어 주어야 했었다. 하지만 Oracle 9i 버전부터는 이 문제가 해결되어 Foreign Key 열에 굳이 인덱스를 만들어 주지 않아도 된다.(물론 성능을 위해서는 인덱스가 필요하며, 사실 Foreign Key 열들은 대부분 인덱싱 대상이기도 하다.)

OLTP 시스템에서 Foreign Key 는 DML 작업간 Validation 검사부하의 성능오버헤드가 있지만 무시할만한다. 오히려 어떤 쿼리들에서는 Foreign Key 정보가 옵티마이저에게 보다 나은 실행계획을 만들도록 도움이 되기도 한다.

대량데이터를 로딩할 때 Foreign Key 가 있으면 Validation 검사 부하가 있으므로 성능이 느려진다. 이것은 로딩 전에 Foreign Key 를 disable 시켰다가 로딩후 다시 enable 시키면 되겠다.

그러나, 성능이 매우 중요한 증권거래시스템 같은 경우엔 Foreign Key 를 달지 않는게 더 나을 것이다.

2. 기존 데이터들이 Referential Integrity 정합성에 맞지 않아서 Foreign Key 를 달고 싶어도 달 수 없다는 이유가 있다.

이 경우 Foreign Key 를 달 때(또는 Alter 명령으로 Enable 시킬 때) Enable Novalidate 옵션으로 생성해 주면 해결된다. 디폴트는 Enable Validate 인데 의미는 기존 데이터들에 대해 Validation 검사수행 후 성공하면 Enable 시킨다는 뜻이다. 반면에 Enable Novalidate 는 기존 데이터들에 대한 Validation 검사를 수행하지 않고 Foreign Key 를 Enable 시키므로 이 문제를 회피할 수 있다.

대량의 데이터 로딩시 Foreign Key 정합성 검사시간을 생략하고자 할 때에도 사용하면 좋은 옵션이다. 로딩 전 Disable 시키고 로딩 후 이 옵션으로 Enable 시키는 것이다.

3. 개발할 때 Foreign Key 가 있으면 불편하다는 이유도 있다.

개발할 때 Foreign Key 때문에 불편하다는 이유는 이해할만 하다. 하지만 Foreign Key 가 매우 중요한 RDBMS 시스템에서 이 정도의 불편은 감수해야 하지 않을까. 어쨋든, Foreign Key 때문에 불편해지는 경우는 두 가지 정도 같다.

첫째, 테스트 데이터 생성시 부모테이블에 먼저 부모 Row 를 생성하고 나중에 자식테이블에 자식 Row 를 생성해야 하는데, 이게 번거로울 수 있을 것이다. 부모 Row 없이 자식테이블에만 테스트 Row 를 생성하고 테스트하면 편리하기 때문이다.
이 문제는 개발기간 중엔 Foreign Key 를 Disable 시켜 놓으면 해결될 것이다.

둘째, 트랜잭션 안에서 부모테이블과 자식테이블의 관계에 따라 DML 작업의 순서가 필요해지기 때문이다. 예를 들어 Insert 작업이라면 부모 Row 먼저 자식 Row 나중의 순서로 작업해야 하고 Delete 라면 자식 Row 먼저 부모 Row 나중의 순서로 작업해야 한다. Foreign Key 가 없다면 이런 순서에 얽매이지 않고 개발할 수 있을 것이다.

여기에도 해결책은 있다. Foreign Key 를 만들 때 Deferred 옵션으로 만들면 된다.
Deferred 의 의미는 Validation 검사를 트랜잭션 단위로 한다는 뜻이다. 쉽게 말해 Commit 할 때 정합성 검사를 수행한다는 뜻이다. 디폴트는 Not Deferred 이다. Not Deferred 의 의미는 문장단위로 검사한다는 뜻이다. 즉 매 문장(Insert, Update, Delete) 마다 검사하기 때문에 순서가 맞지 않으면 오류가 발생되는 것이다. 반면에 Deferred 는 Commit 할 때 검사를 수행하므로 문장들의 순서는 상관없는 것이다.

Foreign Key 를 Deferred 로 만들어서 Disable 시켜놓은 후 개발하고 개발이 끝나고 테스트하기 전에 Enable 시키면 개발단계에서의 불편들을 해결할 수 있을 것이다.

물론 최선은, 다소 불편하더라도 Foreign Key 를 디폴트(Not Deferred)로 만들어서 Enable 시켜놓은 상태에서 코딩하는게 좋다. 이렇게 하면 무엇보다 부모 자식테이블 간에 발생하는 Deadlock (ORA-60 에러) 을 예방할 수 있기 때문이다. 복수 테이블 트랜잭션에서 발생하는 Deadlock 은 대부분 테이블들의 DML 작업 순서가 서로 다른 두 트랜잭션 사이에서 발생하기 때문이다. 일단 오픈후 Deadlock 이 발생되면 해결하는게 만만치 않다.

정리하며

Foreign Key (Referential Integrity) 는 Relational 데이터베이스의 핵심이자 꽃이라고 할 수 있다. 이 기능 때문에 데이터베이스는 데이터의 품질을 유지할 수 있기 때문이다. 또한 유지보수에서도 Foreign Key 가 있는 경우와 그렇지 않은 경우의 생산성은 큰 차이가 있다. 간혹 ERD 가 없어서 ERD 를 Reverse 로 그려내야 하는 경우가 있다. 이 경우 Foreign Key 의 존재유무는 엄청난 차이를 만들어 낸다.

요즘엔 공공 프로젝트들에서는 감리를 받기 때문에 대부분 Foreign Key 를 기본적으로 생성한다. 하지만 아직까지도 Foreign Key 없이 수행하는 대형 금융프로젝트들도 있다. 이런걸 볼 때 마음이 좀 안타깝다. 지금이라도 달기를 권하면 이미 개발 끝나고 테스트 단계라서 Foreign Key 를 달기엔 너무 늦었다는 대답을 듣곤 한다.
위에서 제시한 해결책들을 가지고 더 설득해서 달게 되는 경우도 있지만, 더 많은 경우 그냥 Foreign Key 없이 오픈한다. 어쩌면 관성 때문일 수도 있을 것 같다. 과거 버전부터 주욱 Foreign Key 없이 개발했으니까.

하지만 데이터베이스 시스템 개발자라면 또는 DBA 라면 더욱 더, 이 Foreign Key 없이 시스템을 구축하는 것에 대해 진지하게 고민해야 하지 않을까. 정말로 피할 수 없는 이유들이 있다면 모르겠지만 필자가 보기에 딱히 그런 이유들은 별로 없는 것 같기에 말이다.


[예제] 위에서 설명한 옵션들을 이용해 Foreign Key 를 생성하고 테스트


create table emp2 as select * from emp;

--F.K. 제약조건 생성
alter table emp2
add constraint EMP_DEPT_FK foreign key (DEPTNO)
references DEPT(DEPTNO) 
deferrable initially deferred → commit 할때 검사
enable novalidate → 새로 들어오는 row 만 검사
;

--commit 할때 검사하는지 테스트
insert into emp2(empno, deptno) values (9999, 99);  → 자식먼저
insert into dept(deptno, dname) values (99, 'test');  → 부모나중

commit; → 성공!

--딕셔너리에서 정보조회 방법
select STATUS, DEFERRABLE, DEFERRED, VALIDATED
from user_constraints
where constraint_name = 'EMP_DEPT_FK'
;