mysql批量update会逐行触发触发器,导致性能急剧下降;应禁用触发器改用生成列、应用层传参或异步处理,必须保留时仅允许纯计算的before触发器。

触发器在批量UPDATE里被逐行调用,根本停不下来
MySQL 对 UPDATE 批量操作执行触发器时,并不是“批处理一次”,而是对每一行都单独调用一次触发器体。哪怕你写的是 UPDATE t SET x = 1 WHERE id IN (1,2,3,...,10000),只要表上有 BEFORE UPDATE 或 AFTER UPDATE 触发器,它就会执行 10000 次——每行一次,串行执行,无法并行或跳过。
常见错误现象:SHOW PROCESSLIST 里看到大量状态为 Updating 的线程卡住;慢查询日志中同一条 UPDATE 出现多次,每次耗时稳定在几毫秒以上;INFORMATION_SCHEMA.PROFILING 显示触发器逻辑占总耗时 70%+。
- 触发器里只要含一次
SELECT(哪怕查的是小表),单次开销就可能从 0.3ms 拉到 8ms+,10000 行就是 80 秒 -
NEW.updated_at = NOW()这种看似无害的赋值,在高并发下会因系统时钟函数争用导致微小延迟累积 - 如果触发器还调用了
READS SQL DATA类型的自定义函数,每次调用都会隐式开启子事务,放大锁和上下文切换成本
直接禁用触发器比优化它更有效
很多团队花时间给触发器加索引、拆逻辑、缓存查询结果,但问题根源不在触发器写得不够好,而在于它本就不该承担批量场景下的逻辑分发职责。MySQL 触发器的设计定位是“轻量、确定、单行响应”,不是“业务协调中枢”。
真实场景中,ON DUPLICATE KEY UPDATE、INSERT ... SELECT、临时表 JOIN 更新等批量原生语法,本身就绕过了触发器执行路径——只要没显式写 UPDATE,就不会触发。
- 把触发器里的字段自动填充(如
updated_at)改用GENERATED COLUMN:例如updated_at DATETIME AS (NOW()) STORED,MySQL 8.0+ 支持,写入时自动计算且不走触发器 - 审计类字段(如
created_by)不要靠触发器从 session 变量取,而是在应用层INSERT INTO t (..., created_by) VALUES (..., @user_id)显式传入 - 统计类更新(如订单数累加)彻底移出 DB:用应用层异步任务或
BINLOG解析(Canal/Maxwell)监听变更,避免和主 DML 绑定在同一事务
必须保留触发器时,只允许 BEFORE + 纯计算
如果因历史约束无法移除(比如金融系统强审计要求),那就把触发器压缩到只剩最底线能力:仅 BEFORE INSERT/BEFORE UPDATE,且体内禁止任何表访问、函数调用、条件分支外的副作用。
典型安全写法示例:
DELIMITER $$
CREATE TRIGGER user_safe_update BEFORE UPDATE ON users
FOR EACH ROW
BEGIN
IF OLD.status != NEW.status THEN
SET NEW.updated_at = NOW();
END IF;
END$$
DELIMITER ;
- 删掉所有
SELECT、INSERT、UPDATE、CALL语句;连IF NOT EXISTS(...)这种隐式查询都要干掉 - 禁止使用
RAND()、UUID()、SYS_GUID()等非确定函数,否则主从不一致风险极高 - 字段赋值只依赖
OLD/NEW已有值或内置确定函数(CONCAT、REPLACE、UPPER) - 上线前用
mysqlslap或真实批量数据压测:1000 行UPDATE耗时是否稳定在 50ms 内
批量UPDATE本身也要防锁升级,别让触发器背锅
很多人以为性能差全是触发器的问题,其实常被忽略的是:UPDATE 语句自己就没走索引,导致扫描全表,进而让触发器被调用几万次——这时删掉触发器也救不回性能。
检查方法很简单:EXPLAIN FORMAT=TREE 看执行计划是否走了索引;SHOW ENGINE INNODB STATUS\G 查 TRANSACTIONS 部分是否有长事务锁住大片间隙。
- 别用
WHERE created_at > '2025-01-01'这种条件批量更新,除非created_at有高效索引且范围极小 - 优先用主键分段:
UPDATE t SET status = 1 WHERE id BETWEEN ? AND ? AND status = 0,每次LIMIT 1000并显式COMMIT - 临时表
JOIN更新时,确保tmp_ids表有PRIMARY KEY,否则JOIN退化为全表扫描,触发器照样被扫爆
真正难处理的,是那些既不能删触发器、又没法改业务逻辑的存量系统——这时候与其硬扛,不如在应用层加一层“批量操作代理”,把原始 UPDATE 拆成带 WHERE id IN (…) 的多条小语句,主动控制触发器调用次数上限。











