MySQL原生仅靠触发器可实现字段级审计,但需用BEFORE触发器安全获取OLD/NEW值,避免JSON序列化失败、事务断裂等问题,并通过条件过滤、异步落库等优化性能。

MySQL 本身不内置字段级变更审计能力,触发器是唯一能稳定捕获“哪个字段被改了、从什么值变成什么值”的纯原生方案。但直接上触发器容易踩坑:性能抖动、JSON 序列化失败、事务一致性断裂、甚至触发器自己被 UPDATE 连环触发。
BEFORE UPDATE 触发器里怎么安全取 old_data 和 new_data
必须用 BEFORE 而不是 AFTER —— AFTER 阶段 NEW.id 等字段虽已确定,但 OLD 在 INSERT 中不可用,且无法在触发器内回滚主表操作;而 BEFORE 阶段 OLD 和 NEW 均可用,还能主动 abort(通过 SIGNAL 抛错)。
- OLD.字段名 只在 UPDATE / DELETE 中有效,INSERT 中会报
Unknown column 'OLD.id' in 'field list' - NEW.字段名 在 INSERT / UPDATE 中都可用,但 UPDATE 中若某字段没出现在 SET 子句里,NEW.字段名 值 = OLD.字段名 值(即未变)
- 别用
SELECT JSON_OBJECT('id', OLD.id, 'name', OLD.name)直接拼 —— 字段多、含 NULL 或 BLOB 时会出错;改用JSON_OBJECTAGG()+CONVERT(... USING utf8mb4)显式转码 - 敏感字段(如
ssn、password_hash)建议单独判断:只在IF OLD.ssn != NEW.ssn THEN ... END IF;块里写入审计,避免日志膨胀
audit_log 表设计要防 JSON 写入失败和查询慢
用 JSON 类型存 old_data/new_data 看似方便,但 MySQL 5.7+ 对 JSON 字段的索引支持有限,且 INSERT INTO audit_log VALUES (..., JSON_OBJECT(...)) 在字段含二进制或超长文本时会 silently 截断或报 Invalid JSON text。
- 把
old_data和new_data拆成两个MEDIUMTEXT字段,写入前用JSON_VALID()校验,不合法就 fallback 为'{"error":"invalid_json"}' -
record_id别盲目用VARCHAR(255)—— 若主键是BIGINT,直接存数字字符串即可;若是 UUID,确保入库前已LOWER(REPLACE(uuid, '-', '')) - 加复合索引:
CREATE INDEX idx_audit_table_action_time ON audit_log (table_name, action_type, change_timestamp);,查某张表的最近 10 条更新必须快 - 避免在触发器里调
CURRENT_USER()—— 它返回「连接用户」,不是「应用层传来的操作人」;真要溯源,得靠应用在 SQL 里显式传参(如SET @audit_user = 'api-service-23';),再在触发器中读@audit_user
触发器性能崩了怎么办:跳过非敏感字段、批量合并、异步落库
每行 UPDATE 都触发一次完整 JSON 构造 + INSERT,QPS 上千时 audit_log 表 I/O 会成为瓶颈,甚至拖垮主业务表写入。
- 在触发器开头加条件过滤:
IF NOT (OLD.email NEW.email OR OLD.phone NEW.phone OR OLD.sec_level NEW.sec_level) THEN LEAVE proc_label; END IF;,只审你真关心的字段 - 别在触发器里做复杂逻辑 —— 比如调存储过程解析 JSON 差异、查用户表补姓名,这些全移到应用层或 CDC 后置服务里做
- 终极方案:触发器只写轻量消息到
mysql.general_log或 Redis List,由外部消费者异步解析并落库;这样主事务完全不受审计影响 - 监控指标必看:
SHOW STATUS LIKE 'Com_stmt_execute';和innodb_rows_inserted的突增是否同步 —— 若后者远高于前者,说明审计 INSERT 正在吃资源
最易被忽略的一点:触发器无法捕获 UPDATE ... SET col = col + 1 这类自计算更新中的原始值差异,因为 binlog 里只记最终结果;如果审计要求还原“+1 前的值”,必须在应用层先 SELECT 再 UPDATE,或者改用 ROW 格式 binlog + Debezium 解析 —— 触发器在这里天然有盲区。


















