是,sql触发器默认同步执行并阻塞事务;after/before触发器嵌入原事务,主sql未提交前必须执行完,高并发下会延长事务链路、放大锁竞争与i/o压力。

触发器执行是否在事务内同步阻塞?
SQL 触发器默认是同步执行的,AFTER INSERT 或 BEFORE UPDATE 触发器会直接嵌入到原事务中——也就是说,主 SQL 没提交,触发器逻辑必须跑完。高并发下,这等于把原本轻量的写操作拖进一个更长、更重的事务链路里。
实操建议:
- 用
SHOW CREATE TRIGGER trigger_name查看定义,确认是否含FOR EACH ROW+ 多行 DML(比如循环插入日志表、调用存储函数) - 在压测时开启慢查询日志,过滤出含触发器关联表的语句,观察
Query_time是否显著高于基线(例如从 2ms 跳到 15ms+) - PostgreSQL 用户注意:
AFTER STATEMENT触发器虽不逐行阻塞,但若内部做INSERT INTO audit_log ... SELECT全表扫描,仍可能锁住 MVCC 快照或引发 WAL 压力
触发器里有没有隐式锁升级或全表扫描?
最常被忽略的是触发器代码里的“看起来 harmless”的语句:比如 UPDATE config_table SET last_modified = NOW() —— 表小没事,但若该表被其他服务高频读,就可能成为热点行;又比如 SELECT COUNT(*) FROM orders WHERE status = 'pending',在订单表过亿时,每次 INSERT 都触发一次全索引扫描,I/O 直接拉满。
实操建议:
- 对触发器内所有
SELECT/UPDATE/DELETE语句,用EXPLAIN ANALYZE单独执行,确认是否走索引、是否出现Seq Scan或Lock wait timeout - 避免在触发器中调用
SLEEP()、外部 HTTP 请求、或非确定性函数(如NOW()在复制环境中可能导致主从不一致) - MySQL 用户特别注意:
INSERT ... ON DUPLICATE KEY UPDATE触发的BEFORE INSERT,若内部再做SELECT ... FOR UPDATE,极易引发死锁
触发器是否放大了事务的锁持有时间与范围?
一个 INSERT INTO users 触发器如果顺带 UPDATE stats_summary SET total = total + 1,表面上只改一行,但如果 stats_summary 是单行汇总表,它就成了所有写请求的串行化瓶颈——所有并发 INSERT 都得排队等这行锁。
实操建议:
- 用
SELECT * FROM pg_locks(PostgreSQL)或SELECT * FROM information_schema.INNODB_TRX(MySQL)抓取高并发时的锁等待链,重点看blocking_trx_id是否频繁指向触发器更新的那张表 - 把汇总类逻辑从触发器移到异步队列(如 Kafka + Flink 实时聚合),或改用
INSERT ... SELECT ... ON CONFLICT DO UPDATE(PostgreSQL)这类原子合并语句替代多步触发 - SQL Server 用户需检查触发器是否启用
SET NOCOUNT ON—— 缺失它会导致额外网络往返,放大延迟
有没有更轻量的替代方案?
不是所有业务逻辑都必须用触发器。很多场景下,应用层显式调用或数据库物化视图/计算列更可控、更可观测。
实操建议:
- 审计类需求优先考虑数据库原生功能:
AUDIT_LOG(MySQL 8.0+)、pg_audit(PostgreSQL)、或CHANGE TRACKING(SQL Server) - 状态流转类逻辑(如 “订单创建 → 自动设为待支付”)改用应用层事务包裹:
BEGIN; INSERT ...; UPDATE orders SET status = 'pending' WHERE id = ?; COMMIT;,便于加分布式锁或重试控制 - MySQL 5.7+ / PostgreSQL 12+ 支持生成列(
GENERATED ALWAYS AS),可替代简单计算型触发器,零运行时开销
真正难排查的,往往不是触发器本身有多复杂,而是它把原本分散的 I/O、锁、网络调用悄悄聚合成一个“黑盒放大器”。上线前务必在和生产同规格的压测环境里,用真实流量打满 120% 的 QPS,盯着 pg_stat_statements 或 performance_schema.events_statements_summary_by_digest 看触发器相关 digest 的平均延迟和调用频次——比任何理论分析都管用。










