触发器无法优化出性能优势,本质是批量写入和高并发下的瓶颈;所有调优只是控制损害——砍逻辑、绕开或禁用它。

不能。SQL 中的 INSERT 触发器无法被“优化”出性能优势,它天然就是批量写入和高并发场景下的性能瓶颈;所有所谓“调优”,本质都是在控制损害程度——要么砍逻辑,要么绕开它,要么禁用它。
为什么触发器里写 INSERT 就容易卡住
触发器中执行 INSERT INTO ... SELECT 或向其他表写日志,会强制持有当前事务锁,并把原本可并行的批量操作拖成串行。更糟的是,如果 SELECT 部分没走索引、结果集大、或涉及 JOIN,就会引发全表扫描+行锁堆积,轻则延迟飙升,重则死锁报错 Lock wait timeout exceeded。
- 检查触发器内每条
SELECT是否都命中索引:用EXPLAIN ANALYZE跑一遍实际查询,重点关注Seq Scan行数是否超 1000 - 禁止在触发器里写
INSERT INTO audit_log SELECT ... FROM big_table WHERE status = NEW.status这类语句——哪怕只查一行,status字段没索引就等于锁全表 - 若必须记录日志,改用单行轻量插入:
INSERT INTO audit_log (table_name, op, row_id) VALUES ('orders', 'INSERT', NEW.id),且确保该日志表无外键、无触发器、索引精简
NEW 和 OLD 引用不当会放大开销
看似只是读个字段,但配合函数调用(如 json_extract(NEW.payload, '$.user_id') 或 to_timestamp(NEW.ts_str))就会反复解析、无缓存、不复用,尤其在批量 INSERT 时,10 万行 = 10 万次字符串解析。
- 先赋值给局部变量:PostgreSQL 用
DECLARE v_user_id TEXT := json_extract_path_text(NEW.payload, 'user', 'id');;MySQL 在触发器开头加SET @uid := JSON_UNQUOTE(JSON_EXTRACT(NEW.payload, '$.user.id')); - 避免在
IF/ELSE块里重复取同一个NEW.xxx,尤其是带函数的表达式 - JSON 字段操作前必判类型:
IF JSON_TYPE(NEW.payload) = 'OBJECT' THEN ...,防止对NULL或字符串调用JSON_EXTRACT报错
批量 INSERT 时触发器不是执行一次,而是 N 次
INSERT INTO t VALUES (), (), () 是 3 行插入,触发器逻辑执行 3 次;INSERT INTO t SELECT * FROM staging LIMIT 10000 是 1 万次触发。这不是“慢一点”,是总耗时直接乘以 N——哪怕每次只花 0.5ms,10 万行就是 50 秒。
- 别用
IF EXISTS(SELECT 1 FROM ref WHERE id = NEW.ref_id)做校验——这是标量假设,批量时只校第一行,其余静默通过 - 校验类逻辑必须面向集合:
NOT EXISTS (SELECT 1 FROM inserted i LEFT JOIN ref r ON i.ref_id = r.id WHERE r.id IS NULL) - 能用
ON DUPLICATE KEY UPDATE合并写入的,就别靠触发器做“先查再插” - 真正要同步数据,优先用
MERGE(SQL Server / PostgreSQL 15+)或应用层事务 + outbox 表,而不是依赖触发器
最常被忽略的一点:触发器没有“批量意识”。它生来就为单行设计,强行塞进批量路径,等于让高铁在乡间土路上跑。所有“优化”建议,最终都指向同一个动作——把它从关键写入链上摘下来。


















