mysql批量update触发器导致严重卡顿的根本原因是逐行同步执行——10万行更新触发10万次,引发锁、日志、执行计划全面过载;禁用或绕过触发器(如用insert on duplicate key update)比优化更有效。

不会崩溃,但会严重拖慢甚至卡死——根本原因不是触发器“崩了”,而是它被逐行调用、同步阻塞、无法跳过,10 万行更新就执行 10 万次,锁、日志、执行计划全被拉爆。
MySQL 批量 UPDATE 触发器为什么看起来像“卡死”
MySQL 对 UPDATE t SET x=1 WHERE id IN (1,2,3,...,10000) 这类语句,不会整体触发一次,而是对每一行单独执行一遍触发器体。哪怕只有一行:NEW.updated_at = NOW(),在高并发下也会因系统时钟函数争用,把单次延迟从 0.3ms 累积到几毫秒;10 万行就是几百秒。
-
SHOW PROCESSLIST中大量线程状态为Updating或Executing trigger - 慢查询日志里同一条
UPDATE反复出现,每次耗时稳定在几毫秒以上 -
INFORMATION_SCHEMA.PROFILING显示触发器逻辑占总耗时 70%+ - 如果触发器里有
SELECT(比如查用户余额),每行都扫一次表,IO 和锁直接拉满
SQL Server 中触发器“慢得离谱”的真实瓶颈
SQL Server 的 inserted 表是内存表,没统计信息,触发器里写 UPDATE t2 SET status='done' WHERE t2.id IN (SELECT id FROM inserted) 极易选错执行计划——优化器可能放弃索引,对 t2 全表扫描。50 万行批量 + 全表扫描 × 50 万次 = 几十分钟。
- 显式建临时表并加索引:
SELECT id INTO #tmp_ids FROM inserted; CREATE INDEX ix_id ON #tmp_ids(id);,再用JOIN更新 - 改用
EXISTS更稳定:UPDATE t2 SET status='done' WHERE EXISTS (SELECT 1 FROM inserted i WHERE i.id = t2.id) - 先确认
t2.id上真有索引——很多“慢”其实是主表没索引,不是触发器的问题
禁用触发器不是“优化”,而是必要操作
触发器的设计定位是“轻量、确定、单行响应”,不是“业务协调中枢”。花时间给它加索引、拆函数、缓存结果,解决不了本质问题:它本就不该承担批量逻辑。
- SQL Server:
DISABLE TRIGGER [trg_audit] ON [dbo].[orders],完事再ENABLE;注意别漏USE db_name - PostgreSQL(12+):
ALTER TABLE orders DISABLE TRIGGER trg_status_sync,必须在同一个事务中执行 - MySQL(8.0.23+):
ALTER TABLE orders DISABLE TRIGGER orders_after_insert;老版本只能用RENAME TRIGGER临时改名 - 禁用后务必验证:
SELECT * FROM sys.triggers WHERE is_disabled = 1(SQL Server)或SHOW TRIGGERS LIKE 'orders'(MySQL)
真正绕过触发器的写法比“禁用”更彻底
禁用只是临时手段,而改写 SQL 才是根治——只要不显式执行 UPDATE,就不会触发任何触发器。
- 用
INSERT INTO t SELECT ... ON DUPLICATE KEY UPDATE替代UPDATE,全程不走触发器路径 - 字段自动填充(如
updated_at)改用生成列:updated_at DATETIME AS (NOW()) STORED(MySQL 8.0+) - 审计字段(如
created_by)必须由应用层显式传参,不要靠@session_var或触发器读连接上下文 - 统计类变更(如订单数累加)彻底移出 DB,用 BINLOG 解析(Canal/Maxwell)监听,异步处理
真正容易被忽略的是:即使你用 LIMIT 5000 分批 UPDATE,只要表上有触发器,每一批里的每一行仍会触发一次——批次越小,触发器调用次数越多,上下文切换开销越重。











