혹시 대량 데이터 업데이트할 때 각 행마다 뭔가 작업을 해야 하거나, 아니면 전체 테이블에 대해 한 번만 뭔가 실행해야 할 때가 있었어? 그럴 때 어떻게 구현하는 게 좋을지 고민해본 적 있지? 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를 써야 할 때:
- 트리거가 각 행마다 실행되길 원할 때.
- 실행 로직이 특정 행의 변경에 따라야 할 때.
- 각 행의
OLD와NEW데이터가 필요할 때.
예시: 테이블 변경 로그 남기기, 관련 레코드 자동 생성 등.
FOR EACH STATEMENT를 써야 할 때:
- 전체 연산에 대해 한 번만 실행하고 싶을 때.
- 트리거 로직이 특정 행의 변경과 상관없을 때.
- 성능이 중요하고, 트리거가 여러 번 실행되는 게 부담스러울 때.
예시: 카운터 업데이트, 테이블 메타데이터 계산 등.
실수와 중요한 포인트
트리거 타입을 잘못 쓰는 경우가 많으니까, 다음을 꼭 기억해:
FOR EACH STATEMENT트리거에서OLD와NEW데이터를 쓰려고 하면 에러가 나. 이 변수들은 행 단위 트리거에서만 쓸 수 있어.- 행 단위 트리거(
FOR EACH ROW)는 대량 연산에서 성능이 많이 떨어질 수 있어. 항상 성능을 고려해야 해. - 트리거가 자기 테이블을 다시 변경하면 무한 루프에 빠질 수 있으니까, 재귀 호출에 주의해야 해.
GO TO FULL VERSION