MySQL触发器需手动比对OLD/NEW字段值来捕获具体变更列,注意NULL比较、避免硬编码、审计表应分列存储并建复合索引,操作人信息须由应用层传入用户变量,UPDATE无实际值变则不触发。

触发器怎么捕获具体哪个字段被改了
MySQL 触发器本身不自动告诉你“哪一列变了”,得靠 OLD.col_name 和 NEW.col_name 手动比对。比如审计身份证号、手机号、余额这类敏感列,不能只记录“某行更新了”,必须明确到字段级变更。
实操建议:
- 在
BEFORE UPDATE触发器里逐个判断:IF OLD.id_card != NEW.id_card THEN ... - 注意 NULL 比较:用
IS DISTINCT FROM(MySQL 8.0.17+)或手动处理OLD.col IS NULL != NEW.col IS NULL - 避免写死字段名——如果表结构常变,触发器容易漏审;建议把敏感字段列表单独存配置表,用动态 SQL(但需配合存储过程,且权限和维护成本上升)
- 别在触发器里做复杂逻辑(如远程写日志、调 API),失败会导致主事务回滚
审计日志表设计要避开哪些坑
很多人直接把 OLD 和 NEW 全字段 JSON 存进一个 TEXT 字段,看着省事,查起来要命。
实操建议:
- 至少拆出固定字段:
table_name、row_id(主键值)、changed_column、old_value、new_value、updated_by(从USER()或上下文变量取)、updated_at -
old_value/new_value建议用TEXT,别用VARCHAR(255)截断长内容(比如修改了 JSON 配置字段) - 加复合索引:
(table_name, row_id, updated_at),否则按用户查历史变更时慢得明显 - 不建议用 MyISAM 或 MEMORY 引擎——没事务保障,日志写一半崩溃就丢数据
UPDATE 触发器里怎么拿到操作人信息
MySQL 默认不传“谁改的”,USER() 返回的是连接用户(比如 app@10.0.1.5),不是业务系统里的真实操作人(如 “张三-ID:10086”)。
实操建议:
- 应用层必须显式传参:在 UPDATE 语句前设用户变量,例如
SET @current_user = '张三'; UPDATE account SET balance = ...;,触发器里读@current_user - 确保应用每次请求都重置该变量(防止复用连接时污染)
- 如果用连接池(如 HikariCP),务必开启
useLocalSessionState=true并禁用 auto-commit 复位,否则变量可能丢失 - 不推荐依赖
INFORMATION_SCHEMA.PROCESSLIST反查——性能差、权限受限、不可靠
为什么有些 UPDATE 不会触发审计记录
表面执行了 UPDATE,但触发器没跑,常见于三种情况:批量更新绕过、字段值未真变、权限或语法陷阱。
常见错误现象:
- 执行
UPDATE user SET name = name WHERE id = 123—— 值没变,MySQL 优化掉实际更新,BEFORE/AFTER UPDATE都不触发 - 用
INSERT ... ON DUPLICATE KEY UPDATE时,只有冲突走 UPDATE 分支才触发,且需确认是同一张表的触发器 - 用户没有
TRIGGER权限,创建成功但运行时报ERROR 1359 (HY000): Trigger does not exist(其实是权限不足) - 触发器定义在从库上,但主库 binlog 格式是
STATEMENT,某些函数(如NOW())导致从库执行不一致,触发器失效
真正难缠的是“值看似变了但 MySQL 认为没变”:比如 VARCHAR 字段末尾空格、大小写不敏感 collation 下的字符串比较、JSON 字段格式化差异。这类必须在触发器里用二进制比较或规范化后再判断。


















