触发器无法直接记录执行间隔,只能在AFTER触发器中用clock_timestamp()或NOW(3)写入带时间戳的日志,再由外部程序计算相邻事件时间差;禁止在触发器内查询历史数据或测单条SQL耗时。

触发器不能直接记录执行间隔,必须配合时间戳字段和外部轮询
SQL触发器本身是同步执行的,AFTER INSERT 或 BEFORE UPDATE 触发时,你只能拿到当前语句开始执行的瞬间(比如 NOW()),但无法知道“上一次同类型操作是什么时候”。所谓“执行间隔”,本质是跨事件的时间差,触发器单次运行无状态、无历史上下文——它不是定时器,也不维护变量。
可行路径只有一条:在触发器里写入带时间戳的日志行,再由外部程序(或定时任务)定期查日志表、按 ORDER BY created_at 计算相邻记录的时间差。
- 触发器只负责「打点」:插入一条含
event_type、table_name、created_at的日志记录 - 避免在触发器里做
SELECT MAX(created_at)—— 这会引发锁竞争,严重拖慢主业务表写入 - 日志表必须有索引:
CREATE INDEX idx_log_type_time ON audit_log(event_type, created_at);
MySQL中用BEFORE触发器记录INSERT/UPDATE耗时起点不靠谱
有人想在 BEFORE INSERT 里存一个 start_time 字段,等 AFTER INSERT 再算差值。这在逻辑上就错了:MySQL 不允许在 BEFORE 触发器中修改新行的自增主键或生成列以外的字段(除非是 NEW.col = ... 赋值),更无法把“开始时间”可靠传递到 AFTER 阶段——两个触发器作用域隔离,NEW 在 AFTER 中已不可写,且不可读取 BEFORE 中的局部变量。
正确做法是放弃“单次事务内测耗时”,转而记录离散事件点:
- 所有打点统一走
AFTER触发器,用NOW(3)(毫秒精度)写入created_at - 如果真要测某条 SQL 执行耗时,应该在应用层用
microtime(true)或System.nanoTime()包裹数据库调用,而非依赖触发器 -
SYSDATE()比NOW()更稳妥,它返回函数执行时刻,不受SET TIMESTAMP影响
PostgreSQL中可用序列+触发器模拟轻量级打点,但仍有陷阱
PostgreSQL 支持在触发器中调用 pg_sleep() 或访问 pg_stat_activity,但这些都不能用于性能打点——前者阻塞事务,后者查的是连接级状态,不是具体 DML 行为。真正能用的是 clock_timestamp()(实时系统时间,非事务开始时间)配合序列生成唯一序号:
CREATE OR REPLACE FUNCTION log_event() RETURNS TRIGGER AS $$ BEGIN INSERT INTO perf_log (event_type, table_name, row_id, ts) VALUES (TG_OP, TG_TABLE_NAME, COALESCE(NEW.id, OLD.id), clock_timestamp()); RETURN NEW; END; $$ LANGUAGE plpgsql;
注意三个硬限制:
- 不要在触发器里对大表做
SELECT COUNT(*)或JOIN—— 即使加了索引,高并发下仍易成为瓶颈 - 日志表务必用
UNLOGGED(PostgreSQL)或ENGINE=MEMORY(MySQL,仅限小数据量临时场景),否则 WAL 日志写放大严重 -
clock_timestamp()精度取决于系统时钟,虚拟机环境下可能漂移,生产环境建议同步 NTP
真正影响性能的是日志表写入方式,不是触发器逻辑本身
很多人优化了半天触发器里的 IF 判断或字符串拼接,结果发现瓶颈其实在磁盘 I/O。关键取舍点在于:日志是否需要强持久化?
- 若只要观测趋势(如每分钟 INSERT 次数波动),可用异步消息队列中转(应用层发 Kafka,消费端落库),彻底解除触发器与日志存储的耦合
- 若必须同步落库,把日志表放在独立 SSD 实例上,并关闭
sync_binlog(MySQL)或调大commit_delay(PostgreSQL) - 绝对不要让触发器写入和主业务同一张表的同一 schema 下的分区表——容易引发元数据锁(
MDL)等待
最常被忽略的一点:触发器打点本身会延长事务时间,哪怕只多 0.5ms,在 QPS 上万的写密集型服务中,累积延迟足以触发连接池超时或主从复制 lag 爆涨。上线前必须用真实流量压测对比 SHOW PROFILES 中的 Trigger 阶段时间占比。










