CodeGym /행동 /SQL SELF /트리거에서 에러와 트랜잭션 처리하기: EXCEPTION, RAISE

트리거에서 에러와 트랜잭션 처리하기: EXCEPTION, RAISE

SQL SELF
레벨 58 , 레슨 3
사용 가능

가끔 트리거가 예상치 못하게 동작할 때가 있는데, 그 이유는 다음과 같아:

  • 트리거에 연결된 함수 로직에 논리적 에러가 있을 때.
  • 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 메시지 타입:

  1. DEBUG — 디버깅용 메시지.
  2. NOTICE — 일반 정보 메시지.
  3. WARNING — 경고.
  4. EXCEPTION — 에러 메시지, 함수 실행을 중단시킴.

RAISE 문법:

RAISE <메시지_타입> '메시지';

변수 값도 같이 출력할 수 있어:

RAISE NOTICE 'NEW.id 값 = %', NEW.id;

예시: 트리거에서 값 디버깅하기

예를 들어, students 테이블 업데이트할 때 에러가 나는데, NEWOLD 값이 뭔지 알고 싶다고 해보자. 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();

이제 레코드가 업데이트될 때마다 OLDNEW 값이 보이고, 에러가 나면 이해하기 쉬운 메시지도 받을 수 있어.

트리거에서 트랜잭션

트리거는 트랜잭션 컨텍스트에서 실행돼. 즉, 트리거나 그 함수 안에서 에러가 나면 전체 트랜잭션이 롤백돼. 이 덕분에 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 작업을 느리게 만들 수 있어. 해결 예시: 트리거 로직을 최소화하고, 무거운 쿼리는 피하자.

코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION