CodeGym /행동 /SQL SELF /행 단위 트리거와 테이블 단위 트리거: FOR EACH ROW vs FOR EACH STATEMENT

행 단위 트리거와 테이블 단위 트리거: FOR EACH ROW vs FOR EACH STATEMENT

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

혹시 대량 데이터 업데이트할 때 각 행마다 뭔가 작업을 해야 하거나, 아니면 전체 테이블에 대해 한 번만 뭔가 실행해야 할 때가 있었어? 그럴 때 어떻게 구현하는 게 좋을지 고민해본 적 있지? PostgreSQL에서는 두 가지 방법을 제공해: 행 단위 트리거랑 명령문 단위 트리거. 언제 어떤 걸 써야 하는지 아는 게 데이터베이스 설계, 성능 최적화, 그리고 에러 방지에 진짜 중요해. 같이 알아보자!

행 단위 트리거(FOR EACH ROW)는 각 행마다 INSERT, UPDATE, DELETE 연산이 일어날 때마다 실행돼. 즉, SQL 쿼리가 100개의 행을 건드리면 트리거도 100번 실행되는 거지.

언제 써야 할까?

행 단위 트리거는 바뀐 각 행을 따로따로 처리해야 할 때 유용해. 예를 들면:

  • 각 행의 변경사항을 로그로 남기고 싶을 때.
  • 각 행마다 관련 데이터를 자동으로 업데이트해야 할 때.

예시: 각 행의 변경사항 로그 남기기

학생 테이블이 있다고 해보자:

CREATE TABLE students (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100),
    age INT,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

이 테이블에서 업데이트되는 각 행을 따로따로 students_log라는 테이블에 기록하고 싶어:

CREATE TABLE students_log (
    log_id SERIAL PRIMARY KEY,
    student_id INT,
    old_name VARCHAR(100),
    new_name VARCHAR(100),
    changed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

변경사항을 기록하는 함수:

CREATE OR REPLACE FUNCTION log_student_update()
RETURNS TRIGGER AS $$
BEGIN
    INSERT INTO students_log(student_id, old_name, new_name, changed_at)
    VALUES (OLD.id, OLD.name, NEW.name, CURRENT_TIMESTAMP);
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

FOR EACH ROW 트리거 생성:

CREATE TRIGGER student_update_logger
AFTER UPDATE ON students
FOR EACH ROW
EXECUTE FUNCTION log_student_update();

테스트:

UPDATE students
SET name = '이반 이바노프'
WHERE id = 1;

이 쿼리를 실행하면 students_log 테이블에 변경 내역이 기록돼.

명령문 단위 트리거 (FOR EACH STATEMENT)

명령문 단위 트리거(FOR EACH STATEMENT)는 쿼리 한 번에 대해 딱 한 번만 실행돼. 영향을 받은 행이 몇 개든 상관없어. 쿼리가 100개의 행을 업데이트해도 트리거는 한 번만 실행되는 거지.

명령문 단위 트리거는 이런 경우에 좋아:

  • 전체 연산에 대해 한 번만 뭔가 하고 싶을 때.
  • 집계 데이터 처리나 테이블 전체에 대한 계산이 필요할 때.

예시: 변경 카운터 업데이트

students 테이블의 변경 횟수를 기록하는 카운터 테이블이 있다고 해보자:

CREATE TABLE students_changes_log (
    total_changes INT DEFAULT 0
);
INSERT INTO students_changes_log(total_changes) VALUES (0);

students 테이블에서 UPDATE가 실행될 때마다 이 카운터를 1씩 올리고 싶어.

카운터를 업데이트하는 함수:

CREATE OR REPLACE FUNCTION increment_changes_counter()
RETURNS TRIGGER AS $$
BEGIN
    UPDATE students_changes_log
    SET total_changes = total_changes + 1;
    RETURN NULL; -- 명령문 단위 트리거는 행을 반환하지 않아
END;
$$ LANGUAGE plpgsql;

FOR EACH STATEMENT 트리거 생성:

CREATE TRIGGER update_changes_counter
AFTER UPDATE ON students
FOR EACH STATEMENT
EXECUTE FUNCTION increment_changes_counter();

테스트:

UPDATE students
SET age = age + 1
WHERE age < 20;

이 쿼리를 실행하면 트리거가 한 번만 실행되고, 변경 카운터가 1 증가해.

FOR EACH ROW와 FOR EACH STATEMENT 비교

기준 FOR EACH ROW FOR EACH STATEMENT
실행 레벨 영향받은 각 행마다 전체 연산에 한 번
호출 빈도 행마다 한 번 SQL 쿼리마다 한 번
용도 개별 변경 로그, 행 처리 집계, 메타정보 업데이트
예시 각 행의 변경사항 로그 변경 카운터 업데이트
성능 대량 연산 시 더 느림 대량 연산에 더 효율적

FOR EACH ROW와 FOR EACH STATEMENT 언제 써야 할까?

FOR EACH ROW를 써야 할 때:

  1. 트리거가 각 행마다 실행되길 원할 때.
  2. 실행 로직이 특정 행의 변경에 따라야 할 때.
  3. 각 행의 OLD와 NEW 데이터가 필요할 때.

예시: 테이블 변경 로그 남기기, 관련 레코드 자동 생성 등.

FOR EACH STATEMENT를 써야 할 때:

  1. 전체 연산에 대해 한 번만 실행하고 싶을 때.
  2. 트리거 로직이 특정 행의 변경과 상관없을 때.
  3. 성능이 중요하고, 트리거가 여러 번 실행되는 게 부담스러울 때.

예시: 카운터 업데이트, 테이블 메타데이터 계산 등.

실수와 중요한 포인트

트리거 타입을 잘못 쓰는 경우가 많으니까, 다음을 꼭 기억해:

  1. FOR EACH STATEMENT 트리거에서 OLD와 NEW 데이터를 쓰려고 하면 에러가 나. 이 변수들은 행 단위 트리거에서만 쓸 수 있어.
  2. 행 단위 트리거(FOR EACH ROW)는 대량 연산에서 성능이 많이 떨어질 수 있어. 항상 성능을 고려해야 해.
  3. 트리거가 자기 테이블을 다시 변경하면 무한 루프에 빠질 수 있으니까, 재귀 호출에 주의해야 해.
2
과제
SQL SELF, 레벨 58, 레슨 2
잠금
행 트리거와 명령문 트리거 비교
행 트리거와 명령문 트리거 비교
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION