mysql批量update触发器性能暴跌的根本原因是逐行执行——10万行更新即触发10万次,无法并行、天然串行;禁用比优化更有效,因触发器逻辑占总耗时70%+且含now()等函数会引发时钟争用。

MySQL触发器在批量 UPDATE 时性能断崖式下跌,根本原因不是写法差,而是它被逐行调用——10 万行更新 = 10 万次触发器执行,无法跳过、不能并行、天然串行。直接禁用比优化更有效。
为什么批量 UPDATE 触发器会卡死?
MySQL 对 UPDATE t SET x=1 WHERE id IN (1,2,...,10000) 这类语句,不会“整体触发一次”,而是对每一行单独调用触发器逻辑。哪怕触发器只有一行 NEW.updated_at = NOW(),在高并发下也会因系统时钟函数争用累积延迟;若含 SELECT 或自定义函数,单次开销可能从 0.3ms 拉到 8ms+,10 万行就是 800 秒。
常见错误现象包括:
-
SHOW PROCESSLIST中大量线程卡在Updating状态 - 慢查询日志里同一条
UPDATE反复出现,每次耗时稳定在几毫秒以上 -
INFORMATION_SCHEMA.PROFILING显示触发器逻辑占总耗时 70%+
如何快速绕过触发器执行路径?
真正有效的办法是让 SQL 本身不走触发器逻辑,而不是给触发器加索引或拆函数。
- 用
INSERT ... ON DUPLICATE KEY UPDATE替代UPDATE:只要更新依据是唯一键(主键、业务单号),把目标数据先写入临时表,再通过INSERT INTO t SELECT ... ON DUPLICATE KEY UPDATE完成,全程不触发任何触发器 - 用生成列替代自动填充字段:如
updated_at DATETIME AS (NOW()) STORED(MySQL 8.0+),写入时自动计算,不经过触发器 - 审计字段(如
created_by)必须由应用层显式传参,不要依赖@session_var或触发器读取连接上下文 - 统计类变更(如订单数累加)彻底移出 DB,改用 BINLOG 解析(Canal/Maxwell)或应用层异步任务处理
必须保留触发器时的底线约束
如果因强审计要求无法移除(例如金融系统),那就只允许最简化的 BEFORE 触发器,且体内严禁任何副作用:
- 仅支持
BEFORE INSERT和BEFORE UPDATE,禁用所有AFTER类型 - 体内禁止任何
SELECT、UPDATE、INSERT、DELETE操作 - 禁止调用
READS SQL DATA或MODIFIES SQL DATA的自定义函数 - 禁止
IF/CASE分支以外的逻辑分支(比如循环、异常捕获) - 字段赋值仅限确定性表达式,如
NEW.ts = UNIX_TIMESTAMP(),不用NOW()
分批更新时仍要警惕触发器残留影响
即使你用了 LIMIT 分批执行 UPDATE,只要表上有触发器,每一批里的每一行仍会触发一次。这不是“批次小就安全”,而是“批次越小,触发器调用次数越多,上下文切换开销越重”。
最容易被忽略的一点是:DISABLE TRIGGER 在 MySQL 中并不存在(那是 SQL Server 的语法),MySQL 只能靠 DROP TRIGGER + 手动补逻辑 + CREATE TRIGGER 回滚,但这个过程本身不原子,且无法保证幂等。所以真要禁用,得配合应用层开关或临时修改应用配置,而不是幻想数据库命令一键关闭。











