가끔 트리거가 예상치 못하게 동작할 때가 있는데, 그 이유는 다음과 같아:
- 트리거에 연결된 함수 로직에 논리적 에러가 있을 때.
- DB 제약조건 위반(예: 유니크 위반이나 데이터 타입 불일치 등).
- 트랜잭션 문제로 트리거가 에러 때문에 변경사항을 롤백할 때.
- 트리거가 자기 자신을 호출해서(보통 실수로) 재귀가 발생할 때.
이런 문제를 막으려고 PostgreSQL은 트리거와 그 함수 안에서 에러를 처리할 수 있게 해줘. 그 도구가 바로 EXCEPTION 블록이랑 RAISE 문이야. 오늘은 이걸 예제로 같이 볼 거야.
EXCEPTION 블록으로 에러 처리하기
EXCEPTION 블록은 에러를 잡아서 그걸 처리하는 코드를 실행할 수 있게 해줘. Python이나 Java에서 try-catch 쓰는 거랑 비슷해.
EXCEPTION 블록은 PL/pgSQL 함수에서 이렇게 써:
BEGIN
-- 함수의 메인 코드
EXCEPTION
WHEN <에러_타입> THEN
-- 에러 처리 코드
END;
여기서 <에러_타입>은 네가 처리하고 싶은 특정 에러나 에러 그룹이야(예: unique_violation, division_by_zero 등).
예시: 트리거에서 에러 로깅하기
예를 들어, logs라는 테이블이 있고, students 테이블에 데이터 넣다가 생기는 에러를 거기에 기록하고 싶다고 해보자. 예시는 이래:
로그 테이블 만들기
CREATE TABLE logs (
id SERIAL PRIMARY KEY,
error_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
error_message TEXT
);
에러 처리하는 함수 만들기
CREATE OR REPLACE FUNCTION track_insert_errors()
RETURNS TRIGGER AS $$
BEGIN
-- 메인 코드 시도
BEGIN
-- "에러" 동작 예시: 0으로 나누기
PERFORM 1 / (NEW.some_value - NEW.some_value);
EXCEPTION
WHEN division_by_zero THEN
-- 0으로 나누기 에러가 나면 로그에 기록
INSERT INTO logs (error_message) VALUES ('students에 insert할 때 0으로 나누기 에러');
END;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
트리거 만들기
CREATE TRIGGER before_insert_students
BEFORE INSERT ON students
FOR EACH ROW
EXECUTE FUNCTION track_insert_errors();
이제 students 테이블에 데이터 넣다가 0으로 나누기 에러가 나면, 그 에러가 처리되고 정보가 logs 테이블에 기록돼.
RAISE로 진단과 디버깅하기
RAISE 문은 경고, 에러, 디버깅용 메시지를 출력할 수 있어. 트리거가 어떻게 동작하는지(혹은 왜 안 되는지!) 파악할 때 진짜 유용한 도구야.
RAISE 메시지 타입:
DEBUG— 디버깅용 메시지.NOTICE— 일반 정보 메시지.WARNING— 경고.EXCEPTION— 에러 메시지, 함수 실행을 중단시킴.
RAISE 문법:
RAISE <메시지_타입> '메시지';
변수 값도 같이 출력할 수 있어:
RAISE NOTICE 'NEW.id 값 = %', NEW.id;
예시: 트리거에서 값 디버깅하기
예를 들어, students 테이블 업데이트할 때 에러가 나는데, NEW랑 OLD 값이 뭔지 알고 싶다고 해보자. RAISE를 이렇게 써:
CREATE OR REPLACE FUNCTION debug_student_update()
RETURNS TRIGGER AS $$
BEGIN
RAISE NOTICE 'OLD.id = %, NEW.id = %', OLD.id, NEW.id;
-- 에러를 발생시키는 조건 예시:
IF NEW.some_field IS NULL THEN
RAISE EXCEPTION 'some_field 필드는 NULL이 될 수 없어';
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER after_update_students
AFTER UPDATE ON students
FOR EACH ROW
EXECUTE FUNCTION debug_student_update();
이제 레코드가 업데이트될 때마다 OLD랑 NEW 값이 보이고, 에러가 나면 이해하기 쉬운 메시지도 받을 수 있어.
트리거에서 트랜잭션
트리거는 트랜잭션 컨텍스트에서 실행돼. 즉, 트리거나 그 함수 안에서 에러가 나면 전체 트랜잭션이 롤백돼. 이 덕분에 DB가 부분적으로만 변경되는 걸 막아줘.
근데 이 동작이 가끔 골치 아플 때도 있어:
- 트리거 안에서 에러가 잘못된 데이터 때문이면, 일부 동작만 롤백하고 싶을 때가 있어.
- 트랜잭션 롤백은 트리거뿐 아니라 트리거를 호출한 전체 작업까지 포함된다는 걸 알아둬야 해.
예시: 트리거에서 트랜잭션 사용하기
예를 들어, 어떤 비즈니스 로직이 있는데, students 테이블 업데이트랑 logs에 기록하는 두 가지 작업이 있다고 해보자. 둘 중 하나라도 실패하면 전체 트랜잭션이 롤백돼.
CREATE OR REPLACE FUNCTION transactional_student_update()
RETURNS TRIGGER AS $$
BEGIN
-- 업데이트 시도 로그 남기기
INSERT INTO logs (error_message) VALUES ('id ' || NEW.id || ' 학생 업데이트 시도');
-- 비즈니스 조건 체크
IF NEW.some_value IS NULL THEN
RAISE EXCEPTION 'some_value 필드는 NULL이 될 수 없어';
END IF;
-- 다 성공하면 NEW 반환
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER before_update_students
BEFORE UPDATE ON students
FOR EACH ROW
EXECUTE FUNCTION transactional_student_update();
트리거 작업할 때 자주 나오는 실수와 그걸 막는 방법
개발자들이 자주 하는 실수:
재귀 트리거. 트리거가 변경을 일으켜서 다시 자기 자신을 실행시키는 경우야. 해결 예시: WHEN 조건을 쓰거나, 반복 호출을 막는 플래그를 추가해.
에러 때문에 전체 트랜잭션 롤백. 트리거가 핵심 데이터랑 직접 관련 없을 때는 전체 롤백이 별로야. 해결 예시: EXCEPTION 블록을 잘 써서 처리해.
너무 많은 디버깅 정보. 로그가 지저분해지고 분석이 힘들어져. 해결 예시: RAISE는 개발/테스트 때만 쓰고, 운영에서는 빼.
성능 저하. 복잡한 트리거는 INSERT, UPDATE, DELETE 작업을 느리게 만들 수 있어. 해결 예시: 트리거 로직을 최소화하고, 무거운 쿼리는 피하자.
GO TO FULL VERSION