CodeGym /행동 /SQL SELF /트랜잭션 안에서 프로시저와 함수 호출하기

트랜잭션 안에서 프로시저와 함수 호출하기

SQL SELF
레벨 53 , 레슨 1
사용 가능

요즘 데이터베이스 시스템에서는 비즈니스 로직을 서버 쪽에서 프로시저랑 함수로 구현하는 경우가 많아. PostgreSQL을 쓸 때는 함수와 프로시저의 차이(특히 11버전부터 프로시저가 생긴 이후!)랑, 이게 트랜잭션이랑 어떻게 상호작용하는지 잘 이해해야 해.

아래에서 PostgreSQL 17 기준 공식 문서랑 최신 제한사항에 따라 트랜잭션 메커니즘, 중첩 호출, 프로시저/함수에서 부분 롤백하는 방법의 핵심만 정리해줄게.

핵심 개념: 함수 vs 프로시저

함수 (CREATE FUNCTION)하나의 외부 트랜잭션 안에서만 실행돼. 함수 안에서는 명시적인 트랜잭션 명령(BEGIN, COMMIT, ROLLBACK, SAVEPOINT)을 쓸 수 없어.

  • 모든 변경사항은 외부 트랜잭션 레벨에서만 커밋되거나 롤백돼.
  • 함수 안에서 "부분 롤백" 하려면 BEGIN ... EXCEPTION ... END를 쓰는데, 이건 함수 안에서 커밋하는 건 아니야.

프로시저 (CREATE PROCEDURE) — 서버에서 트랜잭션을 직접 제어하려고 생긴 거야(예: 부분 커밋, 단계별 롤백 등).

  • 프로시저(PL/pgSQL)에서는 COMMIT, ROLLBACK, SAVEPOINT, RELEASE SAVEPOINT를 쓸 수 있어.
  • 중요: PL/pgSQL 프로시저에서는 ROLLBACK TO SAVEPOINT를 쓸 수 없어(문법 에러남).
  • 프로시저는 CALL ... SQL 명령으로만 호출할 수 있고, SELECT나 다른 함수 안에서는 못 불러.

한 함수/프로시저에서 다른 함수/프로시저 호출하는 방법?

함수는 그냥 이름으로 다른 함수를 "투명하게" 호출할 수 있어:

-- 예시: 할인 계산 함수
CREATE OR REPLACE FUNCTION calculate_discount(order_total NUMERIC)
RETURNS NUMERIC AS $$
BEGIN
    IF order_total >= 100 THEN
        RETURN order_total * 0.1;
    ELSE
        RETURN 0;
    END IF;
END;
$$ LANGUAGE plpgsql;

-- 주문 처리 함수가 다른 함수를 호출함
CREATE OR REPLACE FUNCTION process_order(order_id INT, order_total NUMERIC)
RETURNS VOID AS $$
DECLARE
    discount NUMERIC;
BEGIN
    discount := calculate_discount(order_total);
    RAISE NOTICE '할인: %', discount;
    INSERT INTO orders_log (order_id, order_total, discount)
    VALUES (order_id, order_total, discount);
END;
$$ LANGUAGE plpgsql;

모든 게 하나의 외부 트랜잭션 안에서 실행돼! 함수 중 하나라도 에러 나면 모든 변경사항이 롤백돼.

프로시저 호출과 중첩 트랜잭션

프로시저는 다른 프로시저 안에서 CALL ...로 호출할 수 있어(PostgreSQL 17에서는 CALL proc1() -> CALL proc2() 이런 호출 스택이 가능함). 하지만 트랜잭션 규칙은 그대로야:

  • 트랜잭션 명령(COMMIT, ROLLBACK, SAVEPOINT, RELEASE SAVEPOINT)은 프로시저의 최상위 레벨에서만 쓸 수 있어.
  • 트랜잭션 제어가 들어간 프로시저를 이미 활성화된 명시적 트랜잭션(예: 클라이언트에서 autocommit 없이) 안에서 호출하면 COMMIT/SAVEPOINT 시도할 때 에러남.
중요:

프로시저는 함수나 익명 블록(DO ...) 안에서 실행할 수 없어. 오직 CALL 명령으로만!

트랜잭션 제어가 들어간 프로시저 예시

-- 단계별 커밋이 들어간 프로시저(autocommit 연결에서만 동작함)
CREATE PROCEDURE process_batch_orders()
LANGUAGE plpgsql
AS $$
DECLARE
    rec RECORD;
BEGIN
    FOR rec IN SELECT order_id, order_total FROM incoming_orders LOOP
        BEGIN
            -- 각 데이터 묶음을 따로 저장
            INSERT INTO orders (order_id, total) VALUES (rec.order_id, rec.order_total);
        EXCEPTION WHEN OTHERS THEN
            INSERT INTO order_errors(order_id, err_text) VALUES (rec.order_id, SQLERRM);
        END;
        COMMIT;
    END LOOP;
END;
$$;

-- 프로시저 호출
CALL process_batch_orders();

COMMIT 이후에 자동으로 새 트랜잭션이 시작돼.

PL/pgSQL에서 부분 롤백(savepoint 비슷한 동작)

PL/pgSQL(함수, 프로시저 모두)에서는 ROLLBACK TO SAVEPOINT 명령을 지원하지 않아.

코드 일부만 롤백하려면 BEGIN ... EXCEPTION ... END 블록만 써야 해:

BEGIN
    -- 어떤 작업들
    BEGIN
        -- 에러 날 수 있는 작업
    EXCEPTION WHEN OTHERS THEN
        -- 이 블록의 변경사항만 롤백됨
        RAISE NOTICE '블록 안에서 롤백!';
    END;
END;

프로시저에서는 SAVEPOINTRELEASE SAVEPOINT도 쓸 수 있지만, ROLLBACK TO SAVEPOINT는 안 돼. 이건 단계 구분용이고, 실제 제어는 예외 처리로만 가능해.

제한사항과 베스트 프랙티스

  1. 함수는 무조건 원자적(atomic) 작업만: all or nothing. 뭔가 잘못되면 모든 변경사항이 롤백돼.
  2. 프로시저는 CALL로만: 오직 별도의 SQL 명령으로만, SELECT/함수 안에서는 안 돼. 중첩 트랜잭션 제어는 PL/pgSQL 제한을 꼭 지켜야 해.
  3. 부분 롤백은 EXCEPTION만 사용: 공식적으로 권장되고 지원되는 부분 롤백 방식(SAVEPOINT 비슷하게).
  4. 중첩 프로시저는 CALL로 호출할 때만 트랜잭션 제어 가능: 아니면 에러남.

로직과 트랜잭션 상호작용 관련 질문

함수 안에서 "중첩" 트랜잭션 만들 수 있어?

아니. 전부 하나의 트랜잭션에서 실행돼. 부분 롤백은 EXCEPTION 블록만 가능.

함수나 익명 블록에서 COMMIT/ROLLBACK 할 수 있어?

아니, 문법 에러야. 프로시저를 써야 해.

함수에서 프로시저 호출할 수 있어?

아니, 오직 CALL 명령으로만 가능. 함수/SELECT에서는 안 돼.

프로시저에서 ROLLBACK TO SAVEPOINT 할 수 있어?

안 돼! PL/pgSQL에서는 금지야. EXCEPTION 블록을 써.

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