Tự động hóa công việc bằng trigger
Hãy tưởng tượng bạn là admin của một database lớn. Đột nhiên ai đó xóa mất thông tin quan trọng trong bảng, và mọi người hét lên: "Ai làm vậy?!". Để tránh mấy vụ như thế, database cho phép ghi lại các thay đổi và theo dõi xem dữ liệu đã bị làm gì. Tất cả đều nhờ log và audit.
- Ghi log thay đổi giúp lưu lại lịch sử: bản ghi nào bị thay đổi, thay đổi như thế nào và khi nào.
- Audit dữ liệu dùng để kiểm tra sâu hơn, bao gồm cả việc lưu thông tin về người dùng đã thực hiện thay đổi đó.
Bây giờ bạn đã hiểu "tại sao" rồi, cùng học cách làm "như thế nào" nhé.
Tạo bảng log
Trước khi setup trigger, mình cần một bảng để lưu log thay đổi. Ví dụ như sau:
-- Tạo bảng để ghi log thay đổi
CREATE TABLE change_logs (
log_id SERIAL PRIMARY KEY, -- Định danh duy nhất cho mỗi bản ghi log
table_name TEXT NOT NULL, -- Tên bảng bị thay đổi
operation TEXT NOT NULL, -- Loại thao tác: INSERT, UPDATE, DELETE
change_time TIMESTAMP DEFAULT NOW(), -- Thời gian thay đổi
old_data JSONB, -- Dữ liệu trước khi thay đổi (cho UPDATE/DELETE)
new_data JSONB -- Dữ liệu sau khi thay đổi (cho INSERT/UPDATE)
);
Ở đây có gì?
log_id— định danh duy nhất cho mỗi bản ghi log.table_name— mình sẽ lưu tên bảng bị thay đổi.operation— loại thao tác:INSERT,UPDATEhoặcDELETE.change_time— lưu lại thời điểm thay đổi.old_datavànew_data— dữ liệu trước và sau khi thay đổi ở dạng JSON.
Ghi log thay đổi bằng trigger
Bây giờ đã có bảng log rồi, cùng tạo trigger cho một bảng, ví dụ students. Trigger này sẽ ghi lại mọi thay đổi: thêm sinh viên mới, cập nhật hay xóa sinh viên. Cách làm như sau:
- Viết function bằng PL/pgSQL để thêm bản ghi vào bảng log.
- Tạo trigger trên bảng
students.
Function sẽ nhận thông tin về thao tác (INSERT, UPDATE, DELETE), cùng với dữ liệu đã thay đổi (OLD và NEW).
-- Function để ghi thay đổi vào bảng log
CREATE OR REPLACE FUNCTION log_student_changes()
RETURNS TRIGGER AS $$
BEGIN
-- Ghi log thao tác INSERT
IF TG_OP = 'INSERT' THEN
INSERT INTO change_logs (table_name, operation, new_data)
VALUES ('students', 'INSERT', row_to_json(NEW));
-- Ghi log thao tác DELETE
ELSIF TG_OP = 'DELETE' THEN
INSERT INTO change_logs (table_name, operation, old_data)
VALUES ('students', 'DELETE', row_to_json(OLD));
-- Ghi log thao tác UPDATE
ELSIF TG_OP = 'UPDATE' THEN
INSERT INTO change_logs (table_name, operation, old_data, new_data)
VALUES ('students', 'UPDATE', row_to_json(OLD), row_to_json(NEW));
END IF;
RETURN NULL; -- Trả về NULL vì đây là AFTER-trigger
END;
$$ LANGUAGE plpgsql;
Ở đây:
TG_OP— biến đặc biệt chứa thao tác hiện tại:INSERT,UPDATE,DELETE.row_to_json(OLD)vàrow_to_json(NEW)— chuyển dữ liệu dòng sang JSON cho dễ lưu trữ.RETURN NULL— vì đây làAFTER-trigger nên không cần trả về dữ liệu đã thay đổi.
Bây giờ gắn function này với bảng students nhé.
-- Tạo trigger để ghi log thay đổi
CREATE TRIGGER students_log_trigger
AFTER INSERT OR UPDATE OR DELETE ON students
FOR EACH ROW
EXECUTE FUNCTION log_student_changes();
Ở đây có gì?
AFTER INSERT OR UPDATE OR DELETE— trigger chạy sau khi thực hiện các thao tác trên bảngstudents.FOR EACH ROW— trigger chạy cho từng dòng bị thay đổi.EXECUTE FUNCTION log_student_changes()— gọi function để ghi log.
Test trigger
Đến lúc test trigger rồi.
- Thêm bản ghi mới
INSERT INTO students (name, age, grade)
VALUES ('Otto Lin', 20, 'A');
Xem thử bảng log có gì:
SELECT * FROM change_logs;
Ví dụ kết quả:
| log_id | table_name | operation | change_time | old_data | new_data |
|---|---|---|---|---|---|
| 1 | students | INSERT | 2023-10-10 12:00:00 | NULL | {"name": "Otto Lin", "age": 20, ...} |
- Cập nhật bản ghi
UPDATE students
SET grade = 'B'
WHERE name = 'Otto Lin';
Lại kiểm tra bảng log:
SELECT * FROM change_logs ORDER BY change_time DESC;
Kết quả:
| log_id | table_name | operation | change_time | old_data | new_data |
|---|---|---|---|---|---|
| 2 | students | UPDATE | 2023-10-10 12:05:00 | {"name": "Otto Lin", "age": ...} | {"name": "Otto Lin", "age": ..., ...} |
- Xóa bản ghi
DELETE FROM students
WHERE name = 'Otto Lin';
Và lại kiểm tra log:
| log_id | table_name | operation | change_time | old_data | new_data |
|---|---|---|---|---|---|
| 3 | students | DELETE | 2023-10-10 12:10:00 | {"name": "Otto Lin", "age": ...} | NULL |
Ví dụ thực tế
- Ghi log thao tác trên bảng quan trọng: ví dụ bảng tài khoản ngân hàng cần ghi lại mọi thay đổi để chống gian lận.
- Audit hệ thống: bạn có thể lưu log để tuân thủ quy định hoặc phân tích hành vi người dùng.
- Khôi phục dữ liệu: nếu ai đó lỡ tay xóa dữ liệu, bạn có thể phục hồi từ bảng log.
Lưu ý và cạm bẫy
Khi dùng trigger để ghi log, cần chú ý đến hiệu năng. Nếu trigger chạy quá thường xuyên, database sẽ bị tải nặng hơn. Vì vậy:
- Chỉ ghi log trên các bảng thực sự quan trọng.
- Nếu log quá nhiều, hãy nghĩ đến việc lưu trữ hoặc archive log.
Trigger giống như dây đàn guitar: cần chỉnh đúng thì mới hay, nhưng khi đã chuẩn thì tự động hóa công việc nhàm chán và kiểm soát dữ liệu cực tốt luôn.
GO TO FULL VERSION