触发器无法并行是因为其强制同步执行且共享事务上下文、同一线程和同一把锁,MySQL默认FOR EACH ROW逐行触发,1000行插入即1000次串行执行,导致延迟线性放大、TPS骤降。

因为触发器强制串行执行,每条小事务都必须完整跑完触发逻辑才能提交,1000次插入就触发1000次函数调用、1000次锁获取、1000次日志刷盘——这不是慢,是线性放大延迟。
触发器在批量 INSERT 中为何无法并行?
MySQL/PostgreSQL 的 AFTER INSERT 或 BEFORE UPDATE 触发器不是后台任务,它和主 SQL 共享同一事务上下文、同一线程、同一把 log_flush_mutex 锁。即使你用 INSERT INTO t VALUES (...), (...), (...) 一次插 1000 行,InnoDB 仍会逐行调用触发器(除非显式改用 FOR EACH STATEMENT)。实测中,单次触发器平均耗时 0.8ms,1000 行就是 800ms,TPS 直接从 1200 掉到 12。
- MySQL 5.7+ 默认不支持语句级触发器,
FOR EACH ROW是唯一选项 - PostgreSQL 10+ 支持
FOR EACH STATEMENT+NEW TABLE,但需重写逻辑,不能直接访问NEW.id - SQL Server 的
INSTEAD OF可拦截整批操作,但无法自动获得 inserted 表的聚合结果,仍需手动遍历
为什么加了索引,触发器里的 SELECT 还卡住?
触发器内 SELECT balance FROM accounts WHERE id = NEW.account_id 即便命中主键,在 RR 隔离级别下也会加 next-key lock,而如果 accounts 表正被其他高频更新(如充值、退款),就会形成锁等待链。更隐蔽的是:若该查询没走索引(比如用了 WHERE status IN ('pending', 'processing') 且 status 无索引),就会全表扫描+全表加锁,把整个表拖进阻塞队列。
- 所有触发器内
SELECT必须满足:单表、主键或覆盖索引、无OR/NOT IN/LIKE '%' - 优先用
EXISTS (SELECT 1 FROM ...)替代IN,避免 NULL 导致逻辑错误 - 禁止在触发器里
SELECT ... JOIN多张业务表——锁范围会从 1 行扩大到 N 行
触发器调用存储函数为什么会指数级拖慢?
每次调用 CALL check_user_credit(),MySQL 都要重新解析 SQL、校验权限、切换执行上下文、打开游标(如有)、再关闭——这比直接写 SQL 多出 3~5 倍开销。一个含两层嵌套 SELECT 的函数,在 500 QPS 下会让单次触发延迟从 0.8ms 涨到 12ms;若并发升到 2000,平均延迟可能突破 100ms。
- 函数只保留加密、JSON 解析等不可替代场景,并显式声明
READS SQL DATA - 把状态映射类逻辑(如
CASE WHEN status=1 THEN 'active')直接写进触发器体 - 用
SHOW PROFILE FOR QUERY N定位最耗时的一句,别盲目重写整个函数
真正难处理的不是“怎么写快”,而是“哪些事根本不该在触发器里做”。比如扣库存、发通知、写 ES、调 HTTP 接口——这些操作天然不适合强一致性事务边界,硬塞进去只会让数据库替应用背锅。解耦不是加个消息队列就完事,关键是让触发器退化成一行 INSERT INTO sync_log (...) VALUES (...),其余全部交给消费者服务按自身节奏处理。











