본문으로 건너뛰기
16.4 절차적 언어

16.4 절차적 언어

PostgreSQL의 절차적 언어(procedural language, PL)는 SQL 안에서 다른 언어로 함수·프로시저를 작성하게 해 줍니다. PL/pgSQL이 표준, 다른 언어도 확장으로 지원합니다. 핵심은 SQL과 코드의 통합과 트랜잭션 안의 로직.

지원 언어

언어확장TRUSTED메모
PL/pgSQL코어 (plpgsql)TRUSTED기본 활성, Oracle PL/SQL과 유사
SQL코어TRUSTED단순 함수, inline 최적화
PL/Pythonplpython3uUNTRUSTED슈퍼유저만
PL/Perlplperl / plperluTRUSTED / UNTRUSTED
PL/Tclpltcl / pltclu같음
PL/v8plv8UNTRUSTEDJavaScript
PL/Java외부거의 안 씀
PL/RustplrustTRUSTED (sandboxed)신규, AWS Aurora

운영에서 PL/pgSQL이 99%. PL/Python·PL/v8 같은 untrusted는 슈퍼유저만 만들 수 있어 보안 검토 필수입니다.

PL/pgSQL 기본

CREATE OR REPLACE FUNCTION calc_discount(price numeric, status text)
RETURNS numeric AS $$
DECLARE
  discount numeric;
BEGIN
  IF status = 'gold' THEN
    discount := price * 0.2;
  ELSIF status = 'silver' THEN
    discount := price * 0.1;
  ELSE
    discount := 0;
  END IF;
  RETURN discount;
END;
$$ LANGUAGE plpgsql IMMUTABLE;

-- 사용
SELECT calc_discount(100000, 'gold');   -- 20000

함수 vs 프로시저

종류트랜잭션 제어
FUNCTION트랜잭션 안에서 실행. BEGIN·COMMIT 직접 못 함
PROCEDURE (PG 11+)COMMIT·ROLLBACK 가능
-- 프로시저
CREATE OR REPLACE PROCEDURE batch_update()
LANGUAGE plpgsql
AS $$
BEGIN
  FOR i IN 1..100 LOOP
    UPDATE orders SET ... WHERE id_batch = i;
    COMMIT;   -- 매 배치마다 commit
  END LOOP;
END;
$$;

CALL batch_update();

큰 배치를 commit 단위로 쪼개 락·WAL 부담 줄입니다.

트리거

함수가 트리거의 콜백.

CREATE OR REPLACE FUNCTION trg_audit_orders()
RETURNS trigger AS $$
BEGIN
  INSERT INTO audit_log(table_name, op, user_name, timestamp, row_data)
    VALUES (TG_TABLE_NAME, TG_OP, session_user, now(), to_jsonb(NEW));
  RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER orders_audit
  AFTER INSERT OR UPDATE OR DELETE ON orders
  FOR EACH ROW EXECUTE FUNCTION trg_audit_orders();

운영 시 트리거 성능 영향 — 무거운 로직 회피합니다. 단순 audit·denormalization 정도.

SQL 함수 vs PL/pgSQL

-- SQL 함수
CREATE FUNCTION get_user(p_id int) RETURNS users AS $$
  SELECT * FROM users WHERE id = p_id LIMIT 1;
$$ LANGUAGE sql STABLE;

-- PL/pgSQL 함수
CREATE FUNCTION get_user_plpgsql(p_id int) RETURNS users AS $$
DECLARE
  rec users;
BEGIN
  SELECT * INTO rec FROM users WHERE id = p_id LIMIT 1;
  RETURN rec;
END;
$$ LANGUAGE plpgsql STABLE;
측면SQLPL/pgSQL
옵티마이저 inline단순한 경우 inline (호출 비용 0)항상 별도 함수 호출
변수·반복문없음가능
동적 SQL (EXECUTE)안 됨가능
TRIGGER 사용안 됨가능
디버깅어려움RAISE NOTICE

권장: 가능한 한 SQL 함수, 복잡한 로직만 PL/pgSQL.

volatility

표시의미
IMMUTABLE입력이 같으면 항상 같은 결과. 옵티마이저가 constant folding
STABLE한 트랜잭션 안에서 같은 결과. plan 캐시 가능
VOLATILE (기본)매번 다를 수 있음. 매번 호출

정확히 분류해야 옵티마이저가 최적화 + expression index 사용 (6.6).

SECURITY DEFINER vs INVOKER

CREATE FUNCTION log_event(event text) RETURNS void AS $$
  INSERT INTO audit_log(event, session_user, timestamp)
    VALUES (event, session_user, now());
$$ LANGUAGE sql
SECURITY DEFINER
SET search_path = pg_catalog, public;

9.4 참고합니다. 정해진 인터페이스로만 보호 데이터 접근 가능하게.

동적 SQL — EXECUTE

CREATE FUNCTION dynamic_query(p_table text, p_id int)
RETURNS setof users AS $$
BEGIN
  RETURN QUERY EXECUTE
    format('SELECT * FROM %I WHERE id = $1', p_table)
    USING p_id;
END;
$$ LANGUAGE plpgsql;

format() + %I(identifier)·%L(literal)이 SQL injection 방어.

USING 절로 파라미터 — 평문 concat 절대 금지합니다.

예외 처리

BEGIN
  INSERT INTO logs(...) VALUES (...);
EXCEPTION
  WHEN unique_violation THEN
    UPDATE logs SET ... WHERE id = ...;
  WHEN OTHERS THEN
    RAISE NOTICE 'Error: %', SQLERRM;
END;

block 단위 BEGIN·EXCEPTION·END. 트랜잭션 자체와는 다른 개념.

PL/Python — 외부 라이브러리

CREATE EXTENSION plpython3u;   -- 슈퍼유저, untrusted

CREATE FUNCTION fetch_external(url text)
RETURNS text AS $$
  import urllib.request
  return urllib.request.urlopen(url).read().decode()
$$ LANGUAGE plpython3u;

장점: numpy·pandas·requests 등 활용 가능합니다. 단점: GIL, untrusted (보안), 운영 친화 ↓.

운영 권장:

  • 외부 호출은 PostgreSQL 안에서 하지 말 것 — 트랜잭션 안 외부 API = 사고
  • 무거운 계산은 application 또는 별도 worker

운영 시 주의

주의메모
트리거의 무거운 로직INSERT/UPDATE 비용 폭증
함수 안의 외부 호출트랜잭션 안 외부 API 사고
함수의 STABLE/IMMUTABLE 잘못 분류옵티마이저 캐시 오작동 — 정확성 위험
SECURITY DEFINER + search_path 누락권한 escalation
동적 SQL의 SQL injectionformat() + USING 필수
PL 함수의 plan 캐시첫 호출의 generic plan에 묶여 다음 호출 성능 ↓ — SET plan_cache_mode

디버깅

RAISE NOTICE 'value = %', some_var;
RAISE WARNING 'attention';
RAISE EXCEPTION 'fatal — abort';

-- 트랜잭션 안 출력
SET client_min_messages TO NOTICE;

복잡한 로직은 plpgsql_check 확장으로 정적 분석합니다.

함수 vs application 코드

함수에 두는 게 좋음application에 두는 게 좋음
데이터에 가까운 단순 변환비즈니스 로직 일반
트리거 콜백외부 시스템 호출
정확성·일관성 필수자주 변경되는 로직
set-based 처리 (성능)다양한 client 지원

PostgreSQL 함수에 비즈니스 로직 폭주는 stored procedure 안티패턴의 위험합니다. 균형이 핵심입니다.

정리

  • PL/pgSQL이 표준입니다. SQL 함수는 단순 case에 inline 효율
  • 함수는 트랜잭션 안 — 프로시저(PG 11+)는 commit 가능
  • 트리거의 무거운 로직 회피
  • SECURITY DEFINER + search_path 표준 패턴
  • 동적 SQL은 format() + USING으로 injection 방어
  • 외부 호출은 함수 안에 두지 말 것

Part XVI 확장과 응용이 끝났습니다. 다음 Part XVII에서는 현대의 표준 — 클라우드 PostgreSQL을 봅니다.