触发器无法记录dml耗时,只能记录事件发生时间;真正耗时需依赖performance_schema或慢日志;触发器仅适合作为轻量级事件采集钩子,而非性能监控工具。

不能直接用触发器记录“耗时”,因为触发器本身没有执行时间上下文,NOW() 或 SYSDATE() 只能打点时间戳,无法获取语句级执行时长;真正可行的是用触发器捕获 DML 事件并写入日志表,再配合外部手段(如慢日志、performance_schema)反向关联耗时。
触发器里只能记「发生了什么」,不能记「花了多久」
MySQL 触发器在语句执行过程中嵌入运行,它不感知主语句的开始/结束时间,也没有内置的计时器函数。你写 INSERT INTO dml_log (table_name, op_type, ts) VALUES ('orders', 'INSERT', NOW()),得到的是触发器被调用那一刻的时间,不是整条 INSERT 语句的执行起点或终点。
- 常见错误:以为在
BEFORE INSERT记一次NOW(),AFTER INSERT再记一次,相减就能得耗时 —— 这完全不可靠,因为事务可能还没提交,锁等待、刷盘、binlog写入等都在 AFTER 之后发生 - 真实耗时包含:解析、优化、加锁、行查找、引擎层写入、redo log 刷盘、binlog 写入、事务提交等待 —— 这些大部分发生在触发器生命周期之外
- 如果你真需要毫秒级 DML 耗时,必须依赖
performance_schema.events_statements_history_long或开启slow_query_log并设long_query_time = 0,而不是靠触发器
用触发器记录 DML 事件时,必须避开的三个硬伤
即使只做“事件记录”,触发器也容易因设计不当引入性能抖动或数据错乱:
-
INSERT INTO audit_log语句没走索引,比如日志表按created_at查询但没建索引,后续查“某分钟内哪些表被更新”就会全表扫 - 在
AFTER UPDATE里又去UPDATE audit_log自身,形成隐式递归(MySQL 默认禁止,但若用SQL SECURITY DEFINER且权限宽松,可能绕过检查导致死锁) - 误把业务字段当触发条件,例如:
IF NEW.status != OLD.status THEN INSERT ...,但status是 TEXT 类型且没索引,每次比较都触发全字段拷贝和字符集转换,CPU 拉高
真正能落地的轻量方案:触发器 + 外部聚合
想统计“每张表每分钟的 DML 次数及典型操作类型”,触发器可以做干净的原始事件采集,但耗时分析必须交给外部工具:
- 触发器只做一件事:往一张极简日志表插入固定字段,例如
INSERT INTO dml_event_log (db_name, table_name, op_type, event_time) VALUES ('shop', 'orders', 'INSERT', NOW(3))—— 注意用NOW(3)带毫秒,避免全秒精度丢失 - 日志表引擎选
BLACKHOLE(如果只是透传给 binlog)或MEMORY(临时汇总),避免 InnoDB 的 redo 和 doublewrite 开销;若需持久化,至少给(db_name, table_name, event_time)加复合索引 - 用
binlog2sql或mysqlbinlog --base64-output=decode-rows -v解析 binlog,提取event_time和实际执行耗时(来自performance_schema的EVENTS_STATEMENTS_HISTORY_LONG表 joinTHREAD_ID关联)
触发器不是性能监控工具,它是事件钩子。想靠它抓耗时,就像用门铃判断电梯运行时间——铃响了,不代表人已到。关键路径上的耗时,得从 performance_schema 或 slow log 里挖,触发器顶多帮你标出“哪扇门被敲过”。











