Đôi khi trigger hoạt động không như mong đợi, và điều này có thể do:
- Lỗi logic trong function gắn với trigger.
- Vi phạm constraint của database (ví dụ: vi phạm unique hoặc sai kiểu dữ liệu).
- Vấn đề về transaction, khi trigger gây rollback thay đổi do lỗi.
- Đệ quy, nếu trigger tự gọi lại chính nó (thường là vô tình).
Để tránh mấy vấn đề này, PostgreSQL cho phép xử lý lỗi bên trong trigger và function của nó. Các công cụ này gồm block EXCEPTION và lệnh RAISE, tụi mình sẽ xem ví dụ cụ thể luôn.
Xử lý lỗi với block EXCEPTION
Block EXCEPTION cho phép bắt lỗi và chạy một đoạn code để xử lý. Nó giống như try-catch trong các ngôn ngữ như Python hoặc Java ấy.
Block EXCEPTION dùng trong function PL/pgSQL như sau:
BEGIN
-- Code chính của function
EXCEPTION
WHEN <loai_loi> THEN
-- Code xử lý lỗi
END;
Ở đây <loai_loi> là loại lỗi hoặc nhóm lỗi bạn muốn xử lý (ví dụ: unique_violation, division_by_zero v.v.).
Ví dụ: ghi log lỗi trong trigger
Giả sử mình có bảng logs để ghi lại lỗi khi insert dữ liệu vào bảng students. Ví dụ như này:
Tạo bảng log
CREATE TABLE logs (
id SERIAL PRIMARY KEY,
error_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
error_message TEXT
);
Tạo function có xử lý lỗi
CREATE OR REPLACE FUNCTION track_insert_errors()
RETURNS TRIGGER AS $$
BEGIN
-- Thử chạy code chính
BEGIN
-- Ví dụ hành động "lỗi": chia cho 0
PERFORM 1 / (NEW.some_value - NEW.some_value);
EXCEPTION
WHEN division_by_zero THEN
-- Nếu lỗi chia cho 0, ghi log lại
INSERT INTO logs (error_message) VALUES ('Lỗi chia cho 0 khi insert vào students');
END;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
Tạo trigger
CREATE TRIGGER before_insert_students
BEFORE INSERT ON students
FOR EACH ROW
EXECUTE FUNCTION track_insert_errors();
Bây giờ, nếu khi insert vào students mà bị lỗi chia cho 0, nó sẽ được xử lý và ghi thông tin vào bảng logs.
Dùng RAISE để chẩn đoán và debug
Lệnh RAISE cho phép in ra thông báo cảnh báo, lỗi hoặc thông tin debug. Cực kỳ hữu ích khi bạn muốn hiểu trigger của mình chạy thế nào (hoặc tại sao nó không chạy!).
Các loại thông báo RAISE:
DEBUG— thông báo debug.NOTICE— thông báo thông tin bình thường.WARNING— cảnh báo.EXCEPTION— thông báo lỗi, sẽ dừng function.
Cú pháp RAISE:
RAISE <loai_thong_bao> 'Thông báo';
Bạn cũng có thể truyền giá trị biến vào:
RAISE NOTICE 'Giá trị NEW.id = %', NEW.id;
Ví dụ: debug giá trị trong trigger
Giả sử khi update bảng students bị lỗi, bạn muốn biết giá trị NEW và OLD là gì. Dùng RAISE như sau:
CREATE OR REPLACE FUNCTION debug_student_update()
RETURNS TRIGGER AS $$
BEGIN
RAISE NOTICE 'OLD.id = %, NEW.id = %', OLD.id, NEW.id;
-- Ví dụ điều kiện gây lỗi:
IF NEW.some_field IS NULL THEN
RAISE EXCEPTION 'Trường some_field không được NULL';
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER after_update_students
AFTER UPDATE ON students
FOR EACH ROW
EXECUTE FUNCTION debug_student_update();
Bây giờ mỗi lần update, bạn sẽ thấy giá trị OLD và NEW, và nếu có lỗi sẽ nhận được thông báo dễ hiểu.
Transaction trong trigger
Trigger chạy trong context của transaction. Nghĩa là nếu có lỗi ở đâu đó trong trigger hoặc function của nó, toàn bộ transaction sẽ bị rollback. Điều này giúp bảo vệ database khỏi thay đổi nửa vời.
Nhưng đôi khi behavior này cũng gây khó chịu:
- Nếu lỗi trong trigger do dữ liệu sai, đôi khi chỉ muốn rollback một phần thôi.
- Cần hiểu rollback transaction sẽ bao gồm cả trigger lẫn thao tác đã gọi trigger đó.
Ví dụ: dùng transaction trong trigger
Ví dụ, bạn muốn thực hiện một logic nghiệp vụ gồm hai thao tác: update bảng students và ghi log vào logs. Nếu một trong hai thao tác fail, toàn bộ transaction sẽ rollback.
CREATE OR REPLACE FUNCTION transactional_student_update()
RETURNS TRIGGER AS $$
BEGIN
-- Ghi log thử update
INSERT INTO logs (error_message) VALUES ('Thử update student với id ' || NEW.id);
-- Kiểm tra điều kiện nghiệp vụ
IF NEW.some_value IS NULL THEN
RAISE EXCEPTION 'Trường some_value không được NULL';
END IF;
-- Nếu ok thì trả về NEW
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER before_update_students
BEFORE UPDATE ON students
FOR EACH ROW
EXECUTE FUNCTION transactional_student_update();
Lỗi thường gặp khi làm việc với trigger và cách tránh
Mấy lỗi dev hay gặp:
Trigger đệ quy. Xảy ra khi trigger thay đổi dữ liệu lại kích hoạt chính nó. Cách giải quyết: dùng điều kiện WHEN hoặc thêm flag để tránh gọi lại.
Rollback toàn bộ transaction do lỗi. Thường không mong muốn nếu trigger không liên quan trực tiếp đến dữ liệu chính. Cách giải quyết: dùng block EXCEPTION hợp lý.
Thông tin debug quá nhiều. Làm log bị loãng, khó phân tích. Cách giải quyết: chỉ dùng RAISE khi phát triển và test thôi.
Giảm hiệu năng. Trigger phức tạp có thể làm chậm thao tác INSERT, UPDATE hoặc DELETE. Cách giải quyết: tối giản logic trigger và tránh query nặng.
GO TO FULL VERSION