자, 친구들! 이제 트리거가 뭔지, 종류가 뭔지, 어떻게 동작하는지, 그리고 여러 작업에 트리거를 직접 만들어본 경험도 있을 거야. 근데 프로그래밍에서 항상 그렇듯이, 뭘 할 수 있는지 아는 것도 중요하지만, 뭘 하면 안 되는지도 꼭 알아야 해. 오늘은 트리거 다루면서 개발자들이 자주 저지르는 실수들을 같이 파헤쳐볼 거야. 이걸 알면 몇 시간, 아니면 며칠이나 디버깅하는 시간을 아낄 수 있을지도 몰라!
트리거 재귀: 트리거가 자기 자신을 호출하는 경우
이건 진짜 초보들이 제일 많이 하는 실수야. 예를 들어, last_modified 컬럼 값을 업데이트하는 트리거를 만들었다고 해보자. 근데 이 값이 바뀌면, 그 업데이트 작업이 또 트리거를 호출해. 이게 무한 루프가 돼서 결국 서버가 스택 오버플로우 에러로 죽어버려.
예시:
CREATE OR REPLACE FUNCTION update_last_modified()
RETURNS TRIGGER AS $$
BEGIN
-- last_modified 필드 업데이트
UPDATE my_table
SET last_modified = NOW()
WHERE id = NEW.id;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER after_update
AFTER UPDATE ON my_table
FOR EACH ROW
EXECUTE FUNCTION update_last_modified();
여기서 뭐가 문제냐면, 함수 안에서 UPDATE를 하면 그게 또 같은 트리거를 불러. 그래서 무한 루프가 생기는 거지.
어떻게 피할까:
OLD를 써서 값이 진짜 바뀌었는지 비교하고, 바뀌었을 때만 업데이트해:
CREATE OR REPLACE FUNCTION update_last_modified_safe()
RETURNS TRIGGER AS $$
BEGIN
-- 값이 바뀌었는지 확인
IF NEW.last_modified IS DISTINCT FROM OLD.last_modified THEN
NEW.last_modified = NOW();
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
트리거 안에서 불필요한 작업을 호출하지 않도록 꼭 확인해!
OLD와 NEW 잘못 사용하기
이 변수들은 트리거 쓸 때 진짜 친구 같은 존재인데, 초보 때는 이걸 잘못 써서 머리 아픈 일이 많아. OLD는 행이 바뀌기 전 데이터를, NEW는 바뀐 후 데이터를 담고 있어.
실수는 주로 이 변수들이 없는 상황에서 쓰려고 할 때 생겨. 예를 들어 BEFORE INSERT 트리거에서는 OLD가 없어. 왜냐면 행이 이제 막 만들어지는 거니까.
실수 예시:
-- INSERT에서는 OLD가 없어서 에러남
CREATE OR REPLACE FUNCTION log_inserts()
RETURNS TRIGGER AS $$
BEGIN
INSERT INTO audit_log (old_data, new_data)
VALUES (OLD.my_column, NEW.my_column);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
어떻게 피할까:
OLD랑 NEW를 언제 쓸 수 있는지 잘 구분해야 해:
OLD는UPDATE랑DELETE에서만 쓸 수 있어.NEW는INSERT랑UPDATE에서만 쓸 수 있어.
한 작업에 여러 트리거 달기
PostgreSQL에서는 같은 작업, 같은 테이블에 여러 트리거를 달 수 있어. 이게 편해 보일 수 있지만, 실제로는 트리거끼리 충돌하거나 같은 데이터를 바꿔서 혼란이 생길 수 있어.
예시:
-- 트리거 1
CREATE OR REPLACE FUNCTION trigger_one()
RETURNS TRIGGER AS $$
BEGIN
-- 트리거 1 로직
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- 트리거 2
CREATE OR REPLACE FUNCTION trigger_two()
RETURNS TRIGGER AS $$
BEGIN
-- 트리거 2 로직
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- 두 개의 트리거 생성
CREATE TRIGGER trigger_one AFTER INSERT ON my_table EXECUTE FUNCTION trigger_one();
CREATE TRIGGER trigger_two AFTER INSERT ON my_table EXECUTE FUNCTION trigger_two();
두 트리거가 my_table에 행이 추가될 때마다 둘 다 실행돼. 로직이 잘 안 맞으면 예측 불가한 결과가 나올 수 있어.
어떻게 피할까:
- 트리거 구조를 미리 잘 설계해.
- 같은 로직을 다루는 트리거라면 하나로 합쳐.
성능 문제
트리거는 연결된 작업마다 추가 연산을 하게 돼. 트리거가 많은 작업이나 데이터가 많은 테이블에 달려 있으면 성능이 확 떨어질 수 있어.
실수 예시:
CREATE OR REPLACE FUNCTION heavy_trigger_function()
RETURNS TRIGGER AS $$
BEGIN
-- 행이 바뀔 때마다 무거운 작업 실행
PERFORM some_heavy_query();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER performance_killer AFTER UPDATE ON huge_table EXECUTE FUNCTION heavy_trigger_function();
어떻게 피할까:
- 트리거 안에서 하는 로직을 최대한 간단하게 만들어. 무거운 작업은 백그라운드 작업으로 빼는 것도 생각해봐.
WHEN조건을 써서 트리거 실행을 제한해:
CREATE TRIGGER optimized_trigger
AFTER UPDATE ON my_table
WHEN (OLD.column_name IS DISTINCT FROM NEW.column_name)
EXECUTE FUNCTION light_function();
트리거와 트랜잭션
트리거는 네 쿼리에서 시작된 트랜잭션 안에서 실행돼. 트리거 안에서 에러가 나면 전체 트랜잭션이 롤백돼. 이게 어떤 상황에서는 유용하지만, 에러 처리가 잘 안 돼 있으면 예상치 못한 문제가 생길 수 있어.
실수 예시:
CREATE OR REPLACE FUNCTION error_prone_trigger()
RETURNS TRIGGER AS $$
BEGIN
-- 일부러 에러 발생
RAISE EXCEPTION '뭔가 잘못됐어!';
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
이 트리거가 실행되면, 네 쿼리의 트랜잭션 전체가 롤백돼.
어떻게 피할까:
트리거 안에서 에러 처리를 추가해서 메인 트랜잭션에 영향이 덜 가게 해봐:
CREATE OR REPLACE FUNCTION safe_trigger()
RETURNS TRIGGER AS $$
BEGIN
BEGIN
-- 에러가 날 수 있는 코드
INSERT INTO another_table VALUES (NEW.data);
EXCEPTION
WHEN OTHERS THEN
RAISE NOTICE '에러가 났지만, 잘 처리했어.';
END;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
실전 팁
트리거는 최대한 단순하게 만들어. 트리거가 너무 크거나 복잡하다고 느껴지면, 함수로 쪼개거나 로직을 다시 생각해봐.
항상 적은 데이터로 트리거를 테스트해. 중요한 테이블에 트리거 달기 전에 꼭 테스트 환경에서 먼저 확인해봐.
트리거에 주석을 잘 달아둬. 몇 달 지나면 너도, 네 동료도 왜 이 트리거를 만들었는지 까먹을 수 있어. 문서화 잘 해두면 머리 안 아파.
애플리케이션 레벨에서 해결할 수 있는 건 트리거로 하지 마. 트리거는 즉시 처리해야 하는 자동화 작업에 좋아. 근데 복잡한 비즈니스 로직은 트리거로 하면 나중에 문제 생길 수 있어.
항상 성능을 체크해. 데이터나 트래픽이 많아지면 트리거가 DB 성능에 미치는 영향도 커지니까, 모니터링 꼭 해.
이 팁들이랑 오늘 배운 내용만 잘 챙기면, 트리거를 그냥 만드는 게 아니라 진짜 제대로, 효율적으로, 그리고 문제 없이 만들 수 있을 거야!
GO TO FULL VERSION