의존성 추적
의존성 추적 (Dependency Tracking)
테이블을 만들다 보면 어느 순간 객체들 사이에 보이지 않는 '연결망'이 생겨요. 외래 키 제약, 뷰, 트리거, 함수 같은 것들이 서로를 물고 있죠. 예를 들어 외래 키 제약이 있는 테이블은 그 제약이 참조하는 테이블에 의존하게 돼요. 그러다 보면 "이 테이블을 지우고 싶은데, 왜 안 지워지지?" 하고 부딪히는 순간이 옵니다. PostgreSQL이 객체 간 의존성을 어떻게 추적하고, DROP을 어떻게 안전하게 만드는지 살펴볼게요.
출처: 공식문서
아직 다른 객체가 의존하는 객체는 지울 수 없어요
복잡한 구조를 만들면 객체 사이에 의존성의 그물이 생겨요. 데이터베이스 구조 전체의 무결성을 지키기 위해, PostgreSQL은 다른 객체가 여전히 의존하는 객체는 DROP할 수 없게 막아요. orders 테이블이 의존하고 있는 products 테이블을 지우려고 하면 이런 오류가 나요.
DROP TABLE products;
ERROR: cannot drop table products because other objects depend on it
DETAIL: constraint orders_product_no_fkey on table orders depends on table products
HINT: Use DROP ... CASCADE to drop the dependent objects too.
이 오류 메시지에는 유용한 힌트가 담겨 있어요. 의존하는 객체를 하나씩 지우는 게 번거롭다면 이렇게 실행하면 됩니다.
DROP TABLE products CASCADE;
그러면 의존하는 객체들, 그리고 다시 그들이 의존하는 객체들까지 재귀적으로 모두 제거돼요. 이 경우에 orders 테이블 자체가 지워지는 게 아니라 외래 키 제약만 사라져요. 외래 키 제약에 의존하는 것이 더는 없기 때문에 거기서 멈추는 거죠. (DROP ... CASCADE가 뭘 할지 미리 확인하고 싶으면 CASCADE 없이 DROP을 실행해 DETAIL 출력을 읽어보면 돼요.)
PostgreSQL의 거의 모든 DROP 명령은 CASCADE를 지원해요. 물론 가능한 의존성의 종류는 객체 유형에 따라 달라져요. 기본 동작(다른 객체가 의존하면 못 지우게 막는 것)을 원한다면 CASCADE 대신 RESTRICT를 써도 돼요.
참고: SQL 표준에 따르면
DROP명령에서RESTRICT나CASCADE중 하나를 반드시 지정해야 해요. 실제로 이 규칙을 강제하는 DB 시스템은 없지만, 기본 동작이RESTRICT인지CASCADE인지는 시스템마다 달라요.
DROP 명령에 여러 객체를 나열하면, CASCADE는 지정된 객체들 밖에 의존 관계가 있을 때만 필요해요. 예를 들어 DROP TABLE tab1, tab2에서 tab2가 tab1을 참조하는 외래 키가 있다고 해도, 성공하기 위해 CASCADE가 필요하진 않아요.
함수 바디에 따라 추적 범위가 달라져요
본문이 문자열 리터럴로 정의된 사용자 정의 함수·프로시저의 경우, PostgreSQL은 함수의 외부에서 보이는 속성(인자 타입이나 결과 타입 같은 것)에 대한 의존성만 추적해요. 함수 본문을 들여다봐야만 알 수 있는 의존성은 추적하지 않죠. 이 예시를 볼게요.
CREATE TYPE rainbow AS ENUM ('red', 'orange', 'yellow',
'green', 'blue', 'purple');
CREATE TABLE my_colors (color rainbow, note text);
CREATE FUNCTION get_color_note (rainbow) RETURNS text AS
'SELECT note FROM my_colors WHERE color = $1'
LANGUAGE SQL;
(SQL 언어 함수에 대한 설명은 공식문서 Section 36.5에서 확인할 수 있어요.) PostgreSQL은 get_color_note 함수가 rainbow 타입에 의존한다는 걸 알고 있어요. 인자 타입이 더는 정의되지 않게 되면 함수도 함께 지워져야 하니까, 타입을 지우면 함수도 강제로 지워져요. 하지만 get_color_note가 my_colors 테이블에 의존한다고는 보지 않아서, 테이블을 지워도 함수는 지워지지 않아요. 이 방식에는 단점도 있지만 장점도 있어요. 테이블이 없어도 함수는 어떤 의미에서는 여전히 유효하고, 실행하려 할 때만 오류가 나요. 같은 이름의 새 테이블을 만들면 함수는 다시 동작하게 되죠.
반면, 본문이 SQL 표준 방식으로 작성된 SQL 언어 함수·프로시저는 함수 정의 시점에 본문이 파싱되어 파서가 인식하는 모든 의존성이 저장돼요. 그래서 위 함수를 이렇게 쓰면,
CREATE FUNCTION get_color_note (rainbow) RETURNS text
BEGIN ATOMIC
SELECT note FROM my_colors WHERE color = $1;
END;
my_colors 테이블에 대한 의존성이 알려지고 DROP에 의해 강제 적용돼요.