能,但需手动建日志表、分操作类型创建独立触发器、显式写入json格式新旧数据,并注意delete用before、update/insert用after,避免无效日志和事务阻塞。

触发器能自动记录增删改,但必须手动建日志表、显式写入、注意事务耦合——它不是开箱即用的审计开关。
必须先创建专用的日志表结构
触发器本身不提供日志存储,你得自己定义 audit_log 表,字段要覆盖关键上下文:
-
id(自增主键) -
table_name(VARCHAR(64),记录被操作的表名) -
operation(ENUM('INSERT','UPDATE','DELETE')或VARCHAR(10)) -
old_data和new_data(用JSON类型存变更前后的整行,MySQL 5.7+ / PostgreSQL 支持;SQL Server 可用NVARCHAR(MAX)存序列化字符串) -
user_host(用USER()或SYSTEM_USER获取当前会话用户) -
created_at(用NOW()或CURRENT_TIMESTAMP)
漏掉 table_name 或 operation,日志就失去可追溯性;用 TEXT 存 JSON 而不用 JSON 类型,后续查某字段值会极难过滤。
INSERT/UPDATE/DELETE 触发器要分开写,且不能共用同一触发器
MySQL 和 PostgreSQL 都不支持单个触发器响应多种操作类型。你必须为每种操作单独创建:
CREATE TRIGGER user_insert_audit AFTER INSERT ON users FOR EACH ROW INSERT INTO audit_log (...) VALUES ('users', 'INSERT', NULL, JSON_OBJECT(...), USER(), NOW());CREATE TRIGGER user_update_audit AFTER UPDATE ON users FOR EACH ROW INSERT INTO audit_log (...) VALUES ('users', 'UPDATE', JSON_OBJECT(...), JSON_OBJECT(...), USER(), NOW());CREATE TRIGGER user_delete_audit BEFORE DELETE ON users FOR EACH ROW INSERT INTO audit_log (...) VALUES ('users', 'DELETE', JSON_OBJECT(...), NULL, USER(), NOW());
注意:DELETE 用 BEFORE 才能读到 OLD 行;UPDATE 的 OLD 和 NEW 都可用;INSERT 只有 NEW。混用时机(比如在 DELETE 里用 AFTER)会导致 OLD 不可用,写入 NULL。
触发器里别调用耗时函数或跨库写入
触发器运行在原事务上下文中,任何阻塞都会拖慢主 SQL。常见雷区:
- 调用外部 HTTP 接口(完全不可行)
- 对大表做
SELECT COUNT(*)统计 - 往另一个数据库的表写日志(跨库事务可能失败,且性能差)
- 在触发器里再调用存储过程,而该过程又含复杂逻辑
安全做法是只做轻量级 JSON 构造和单条 INSERT 到本地 audit_log 表。如果真需要异步落盘,应由应用层监听 binlog 或使用 CDC 工具,而不是塞进触发器。
UPDATE 触发器要判断字段是否真变了,否则日志爆炸
哪怕只执行 UPDATE users SET name=name WHERE id=1;,默认也会触发日志。必须显式比较:
IF OLD.email != NEW.email OR OLD.status != NEW.status THEN INSERT INTO audit_log (...) VALUES (...); END IF;
不加判断,用户批量更新 10 万行(哪怕没实质变更),就会产生 10 万条无效日志。PostgreSQL 可用 OLD IS DISTINCT FROM NEW 简化全行比对;MySQL 则需逐字段写条件,或用 MD5(CONCAT(...)) 做哈希对比(注意 NULL 处理)。
真正麻烦的是多表关联变更场景——比如订单状态变更是由支付表更新引发的,但触发器只在支付表上,你得靠业务字段(如 order_id)去反查并补全上下文,这部分逻辑极易遗漏或出错。











