触发器强制逐行执行导致批量操作串行化,1000行更新即1000次调用,即使空触发器也带来0.1–0.3ms固定延迟;它放大锁范围、隐藏死锁、屏蔽EXPLAIN优化、争抢系统资源,禁用比优化更有效。

触发器强制逐行执行,批量操作变成串行地狱
MySQL 对 UPDATE t SET x=1 WHERE id IN (1,2,3,1000) 这类语句,不会整体触发一次,而是对每一行单独调用触发器逻辑。1000 行 = 1000 次触发器执行;哪怕每次只耗 0.5ms,总延迟就是 500ms。更糟的是,它把本可并行的批量写入,硬生生拖进事务级串行化——TPS 从 1200 直接掉到 80 不是异常,是机制必然。
- 所有
AFTER类型触发器都绕不开这个限制,BEFORE也不能规避逐行上下文切换开销 - 即使触发器 body 是空的,或只写
SET @x = 1,MySQL 仍需做解析、权限检查、变量绑定,实测固定增加 0.1–0.3ms 延迟 - 分批更新(如加
LIMIT 100)并不能缓解问题:批次越小,触发器调用次数越多,上下文切换占比反而更高
触发器悄悄放大锁范围,引发隐性死锁和等待
触发器和主 SQL 共享同一事务上下文,它里面任何一句 SELECT ... FOR UPDATE 或 UPDATE,都会把原本只该锁订单行的事务,额外拖进其他表的锁竞争中。比如 BEFORE UPDATE 里查配置表:SELECT value FROM config WHERE key = 'rate_limit',每次更新都重查,O(1) 变 O(n);AFTER INSERT 里更新用户表:UPDATE users SET last_order_time = NOW() WHERE id = NEW.user_id,主事务锁订单行,触发器又去锁 users 行,另一条路径反着来,立刻触发 Deadlock found when trying to get lock。
解析 MySQL 8.0 的 DDL 脚本,并将表结构导出为 Excel 格式。适用于用户需将 MySQL 的 `CREATE TABLE` 语句转换为结构化的 Excel 文件场景,导出内容包含字段名、数据类型、长度及精度等信息。支持多张表的批量处理,每张表对应 Excel 中的一个独立工作表。适用于数据库文档编写、数据字典生成及模式分析等任务。
-
SHOW PROCESSLIST只显示Updating或Executing trigger,不标具体触发器名,你根本看不到锁在哪卡住 - 触发器内含
SELECT COUNT(*) FROM events WHERE user_id = NEW.user_id,而events(user_id)没索引?那就是全表扫描 + 行锁阻塞其他写入 - 日志表如果用默认 InnoDB + 多个二级索引,在高并发下会迅速成为瓶颈:每写一行都要维护聚簇索引 + N 个二级索引 B+ 树
触发器内非确定性函数争抢系统资源
NOW()、UUID()、RAND() 这类函数在高并发下不是“取个时间”那么简单,它们要争抢全局时钟锁或随机数生成器,单次调用可能多耗 0.3–0.8ms。10 万行更新,光这部分就多等 80 秒。
- 哪怕只是
NEW.updated_at = NOW(),在并发 500+ 场景下,innodb_row_lock_waits会明显飙升,但慢查询日志里只显示主语句耗时变长,你查不到触发器内部 -
EXPLAIN完全不展示触发器内 SQL,你优化了主语句,却对里面三条嵌套SELECT一无所知 - 触发器里写游标?每次触发都重新编译、结果集全加载进内存、
FETCH必须串行等待,还极易引发Waiting for table metadata lock
禁用比优化更有效,且有明确替代路径
触发器逻辑通常占总耗时 70%+,且无法跳过、不能并行、天然串行。与其花时间给触发器加索引或拆函数,不如直接绕过执行路径。
- 用
INSERT INTO t SELECT ... ON DUPLICATE KEY UPDATE替代UPDATE:只要依据是唯一键,全程不触发任何触发器 - 用生成列替代自动填充字段,如
updated_at DATETIME AS (NOW()) STORED(MySQL 8.0+),写入时自动计算,不经过触发器 - 审计字段(如
created_by)必须由应用层显式传参,不要依赖@session_var或触发器读取连接上下文 - 统计类变更(如订单数累加)彻底移出 DB,改用 BINLOG 解析(Canal/Maxwell)或应用层异步任务处理


















