[PostgreSQL] DB Trigger 개념

Lutica_·2024년 5월 12일

PostgreSQL 분해하기

목록 보기
1/1

Introduce

  • Database는 Data를 저장하는 Base를 의미한다.
  • 그런데, Data가 바뀔때마다 뭔가 해주면 더 좋지 않을까?
  • SQL은 데이터를 저장하는데 있어서, 데이터가 바뀔 때마다 뭔가를 실행해주는 기능이 제공되는 경우가 있다.
  • 이번에 분석해 볼 PostgreSQL은 그 기능이 제공되고 있으며,그 기능이 마치 권총의 trigger과 같다 하여 DB trigger라 한다.
  • 이 포스트에서는, PGSQL DB Trigger의 사용법부터, 그것의 c코드 원리를 간단히 분석해볼 예정이다.
  • 필자는 데이타베이스 과목에서 C+을 받았다 통계함수 진짜 싫다.

DB Trigger

무엇이고 왜 쓰는가?

  • DB Trigger는 하나를 바꾸거나/지우거나/넣는다면(Create,Update,Delete) 그에 따른 후속 처리를 진행하는 매개 프로그램이다.
  • 이것은 DB가 당연히 가져야 할 데이터의 정합성을 위하여 가지는 적극적 조치라고 볼 수 있다.

쓰면 무엇이 좋고 나쁜가?

장점

인적오류는 사람이 하기에 발생하는 문제이다.

  • 한행이 바뀔때마다 행해져야 할 작업을 다 해준다는 장점이 있다.

단점

은총탄은 없다.

  • 그런데, 한 행이 바뀔때마다 함수가 호출된다는 점은 overhead로 작동할 가능성이 있다.
  • 한개의 함수면 모를까, 그 작업이 복잡해지거나, 여러함수가 호출된다면 너무 과도한 작업이 행해지고 이는 DB의 성능이 낮아진다는 점을 잊지말자.

사용 상세

이 포스트에서는 PGSQL을 기반으로 설명합니다.

정의

원문 https://www.postgresql.org/docs/current/sql-createtrigger.html

CREATE [ OR REPLACE ] [ CONSTRAINT ] TRIGGER name { BEFORE | AFTER | INSTEAD OF } { event [ OR ... ] }
    ON table_name
    [ FROM referenced_table_name ]
    [ NOT DEFERRABLE | [ DEFERRABLE ] [ INITIALLY IMMEDIATE | INITIALLY DEFERRED ] ]
    [ REFERENCING { { OLD | NEW } TABLE [ AS ] transition_relation_name } [ ... ] ]
    [ FOR [ EACH ] { ROW | STATEMENT } ]
    [ WHEN ( condition ) ]
    EXECUTE { FUNCTION | PROCEDURE } function_name ( arguments )

where event can be one of:

    INSERT
    UPDATE [ OF column_name [, ... ] ]
    DELETE
    TRUNCATE
  • 일단 CREATE [OR REPLACE]문은 schema를 만들거나 대체하는 것을 의미한다.
  • CONSTRAINT가 붙어있다면, 이 Trigger는 무조건 실행 후 발동되는 Trigger이며, 트랜잭션의 끝에 실행된다. 또한, ROW TRIGGER 만 가능하다.
  • { BEFORE | AFTER | INSTEAD OF } { event [ OR ... ] }에서는 발동 타이밍과 이벤트를 정할 수 있다. (이벤트는, INSERT/UPDATE/DELETE/TRUNCATE중 하나이다.)
  • 만약, INSTEAD OF [INSERT...]라면 INSERT대신 이 Trigger가 발생한다.
  • ON table_name : 테이블 이름을 지정할 수 있다.
  • [ NOT DEFERRABLE | [ DEFERRABLE ] [ INITIALLY IMMEDIATE | INITIALLY DEFERRED ] ]: 변경시 초기화 조건에 대한 제약.
  • [ FOR [ EACH ] { ROW | STATEMENT } ] : ROW 단위의 Trigger인지 혹은 STATEMENT단위의 Trigger인지를 의미한다. 이것은 ROW 업데이트 이후 트리거를 발동하는지, 혹은 한꺼번에 업데이트 이후 발동하는지의 차이가 있다.
  • [ WHEN ( condition ) ] : 실행시의 조건을 의미한다.
    -EXECUTE { FUNCTION | PROCEDURE } function_name ( arguments ) : 실행되는 트리거 함수/프로시저를 의미한다.

Trigger Function/Procedure?

https://www.postgresql.org/docs/current/plpgsql-trigger.html

  • 기존 SQL Function/Procedure에서, 각종 명령어들이 추가된다.
  • 대표적으로,
  • NEW : 새롭게 변경된 값이 적용된 행
  • OLD : 대체되는 행의 기존값을 가진 행.
  • 그 외는 43.10.1. Triggers on Data Changes 를 참조하라.

사용예

CREATE TABLE emp (
    empname           text,
    salary            integer,
    last_date         timestamp,
    last_user         text
);

CREATE FUNCTION emp_stamp() RETURNS trigger AS $emp_stamp$
    BEGIN
        -- Check that empname and salary are given
        IF NEW.empname IS NULL THEN
            RAISE EXCEPTION 'empname cannot be null';
        END IF;
        IF NEW.salary IS NULL THEN
            RAISE EXCEPTION '% cannot have null salary', NEW.empname;
        END IF;

        -- Who works for us when they must pay for it?
        IF NEW.salary < 0 THEN
            RAISE EXCEPTION '% cannot have a negative salary', NEW.empname;
        END IF;

        -- Remember who changed the payroll when
        NEW.last_date := current_timestamp;
        NEW.last_user := current_user;
        RETURN NEW;
    END;
$emp_stamp$ LANGUAGE plpgsql;

CREATE TRIGGER emp_stamp BEFORE INSERT OR UPDATE ON emp
    FOR EACH ROW EXECUTE FUNCTION emp_stamp();
  • 이상의 구문은 empINSERT/UPDATE 이전에 행해지는 구문으로서,
  • EACH ROW에 대해 가해지는 사전처리 함수입니다.
  • 새로운 행의 empnamesalaryNULL이라면 RAISE EXCEPTION하고, 또한 salary < 0 일때도 마찬가지의 처리를 합니다.
  • 아니라면, 지급일을 새로운 행에 기록하고 그것을 INSERT 혹은 UPDATE 합니다.

그럼 한 테이블에 두 개 이상의 Trigger를 써도 되는가?

  • 네.
  • 그런데 많이 쓰려한다면 DB의 복잡성이 강화되니 조심하자.
  • MySQL과는 다르게, 우선순위는 정할 수 없으니 함수에 정의하는것을 중요하게 생각하자!

Trigger On Code.

Trigger는 backend/commands/trigger.c에 정의되어 있다.

  • 이제 어느정도 사용법을 알았으니, 이 포스터의 본격적인 목표인 c언어 단에서의 Trigger 동작을 분석해보자.

Create Trigger (진입점)

  • CreateTrigger에 정의되어 있다.
  • CreateTrigger의 return값은 ObjectAddress이다.
  • 이 함수는 CreateTriggerFiringOn를 호출한다.
  • 스키마는 이하와 같다.
ObjectAddress
CreateTrigger(
	CreateTrigStmt *stmt, 
	const char *queryString,
    Oid relOid, 
    Oid refRelOid, 
    Oid constraintOid, 
    Oid indexOid,
	Oid funcoid, 
    Oid parentTriggerOid, 
    Node *whenClause,
	bool isInternal, 
    bool in_partition);

사용되는 자료구조

CreateTrigStmt

  • Trigger의 상태를 정의하는 구조체
/* ----------------------
 *		Create TRIGGER Statement
 * ----------------------
 */
typedef struct CreateTrigStmt
{
	NodeTag		type;
	bool		replace;		/* replace trigger if already exists */
	bool		isconstraint;	/* This is a constraint trigger */
	char	   *trigname;		/* TRIGGER's name */
	RangeVar   *relation;		/* relation trigger is on */
	List	   *funcname;		/* qual. name of function to call */
	List	   *args;			/* list of String or NIL */
	bool		row;			/* ROW/STATEMENT */
	/* timing uses the TRIGGER_TYPE bits defined in catalog/pg_trigger.h */
	int16		timing;			/* BEFORE, AFTER, or INSTEAD */
	/* events uses the TRIGGER_TYPE bits defined in catalog/pg_trigger.h */
	int16		events;			/* "OR" of INSERT/UPDATE/DELETE/TRUNCATE */
	List	   *columns;		/* column names, or NIL for all columns */
	Node	   *whenClause;		/* qual expression, or NULL if none */
	/* explicitly named transition data */
	List	   *transitionRels; /* TriggerTransition nodes, or NIL if none */
	/* The remaining fields are only used for constraint triggers */
	bool		deferrable;		/* [NOT] DEFERRABLE */
	bool		initdeferred;	/* INITIALLY {DEFERRED|IMMEDIATE} */
	RangeVar   *constrrel;		/* opposite relation, if RI trigger */
} CreateTrigStmt;

Oid

typedef unsigned int Oid;

  • Object Id를 의미한다.

CreateTriggerFiringOn

  • CreateTriggerFiringOn는 실제로 Trigger를 만드는 역할을 한다.

호출 타이밍

  • 호출은 HeapTuple ExecCallTriggerFunc을 통해 이루어진다.
  • 자세히는,
static HeapTuple
ExecCallTriggerFunc(TriggerData *trigdata,
					int tgindx,
					FmgrInfo *finfo,
					Instrumentation *instr,
					MemoryContext per_tuple_context)

를 호출하고, HeapTuple이라는 객체를 반환한다.

  • LOCAL_FCINFO에서 호출을 요구하는 함수를 초기화하고,
  • InitFunctionCallInfoData에서 Trigger 정보와 함께 묶은 다음, 묶인 함수(정확히는 함수의 포인터)를 FunctionCallInvoke로 호출한다.
  • 그 다음, 기록 처리등의 작업을 거친 다음, HeapTuple type의 데이터로 return 하여 trigger를 마친다.
profile
해보고 싶고, 하고 싶은 걸 하는 사람

0개의 댓글