foreign key를 사용하는 이유는 참조 무결성을 위해서이다. 연관관계에 있는 테이블에서, 외래키로 지정된 컬럼 데이터가 부모의 기본키 외 다른 값을 가질 수 없게 하여 외래키 컬럼에 저장될 수 있는 데이터를 제어하게 된다.
예를 들어, 유저, 주문 테이블이 있고 주문 테이블에 foreign key로 유저의 primary key를 갖고 있다 하면, 아직 가입 안된 유저 id로 주문테이블에 데이터를 넣으려하면 에러를 발생시켜 해당 데이터 삽입을 막는다. 이를 통해 주문 테이블 내에서 참조하는 값이 존재한다는 것을 보장할 수 있다. 즉 참조하는 테이블의 무결성을 높여주게 된다.
Transaction1 | Transaction2 |
---|---|
begin | begin |
insert into child(id, p_id) value (1, 1); | |
update parent set name="test1" where id = 1; | |
update parent set name="test2" where id = 1; | |
deadlock 발생 |
위의 표에 대한 설명은 아래와 같다.
트랜잭션 1에서 아래 쿼리와 같이 자식테이블에 parent_id를 1로 해서 인서트한다고 해보면
insert into child(id, p_id) value (1, 1);
이때 parent 테이블의 id 1번 로우에 slock이 걸린다.
트랜잭션 2 에서
update parent set name="test1" where id = 1;
위의 쿼리를 실행하면 xlock을 획득 시도하나 slock으로 인해 대기상태 들어간다.
트랜잭션 1 에서
update parent set name="test2" where id = 1;
위의 쿼리를 날린다 해보면, 이 경우 xlock을 획득하려하나 트랜잭션2의 xlock으로 대기에 들어가 결국 데드락이 발생한다.
즉 foreign key으로 인해 락이 다른 테이블로 전파되어 데드락 발생 가능성이 생긴다.
foreign key 컬럼에 인덱스를 반드시 걸어 조회 속도를 높이는 방법으로 최대한 데드락 발생 가능성을 낮춰야 한다.
참조무결성을 db로 풀어야 할까?
참조무결성을 db 레이어에서 정의하게 되면 애플리케이션 레이어가 db를 따라가게 된다. db가 분리될 가능성도, 변경될 가능성도 있다고 할 때, db의 변경이 애플리케이션 레이어에까지 영향을 미칠 수 있다.
무결성이란 관심사가 애플리케이션 레이어에서의 책임, 관심사로 판단될 경우 애플리케이션 내에서 풀고 db에의 의존성을 덜어낼 수 있을 것이다.
물론 참조무결성이 중요하고 cascade, 즉 영속성전이 역시도 필요할 경우에는 당연히 foreign key를 사용해야 한다. foreign key가 좋다 나쁘다를 따지고 싶진 않다. 사실 데이터 무결성을 위해선 꼭 필요한 제약사항이기도 하다. 하지만 위와 같은 이유들로 인해 애플리케이션 단에서 무결성 관리를 철저히 하고 데드락 이슈를 줄일 수 있는 방향으로 서비스를 운영하기도 한다.
데이터 무결성이 깨질 수 있다. 그러므로 애플리케이션 레벨에서 최대한 신경써가며 관리하여 최대한 데이터 무결성을 지킬 수 있도록 많은 노력을 기울여야 한다.