触发器必然拖慢写入,唯一立竿见影的方案是导入前禁用、导入后启用:mysql用alter table t disable trigger,postgresql和sql server同理;禁止跨表查询、join及同表更新,避免死锁与i/o爆炸,复杂逻辑须异步解耦。

触发器必然拖慢写入,这不是“能不能优化”的问题,而是它天生就运行在事务内、同步执行、无法跳过——能做的只有砍逻辑、移出事务、或换执行粒度。
批量插入前必须禁用触发器
INSERT INTO t VALUES (),(),() 插入 N 行,触发器就执行 N 次。10 万行 = 10 万次函数调用 + 上下文切换 + WAL 写入,I/O 和锁开销直接爆炸。
- MySQL:
ALTER TABLE t DISABLE TRIGGER tg_name,导入完再ENABLE TRIGGER - PostgreSQL:
ALTER TABLE t DISABLE TRIGGER tg_name(支持并发 DML,不锁表) - SQL Server:
DISABLE TRIGGER tg_name ON t(只停触发逻辑,不影响约束和索引)
别试图“优化触发器里的循环”,禁用是唯一能立竿见影的方案。
触发器里禁止跨表查询和 JOIN
看似安全的一句 SELECT points FROM user_points WHERE user_id = NEW.user_id,在高并发下就是 I/O 瓶颈:每写一行就查一次,索引再好也扛不住 1000 QPS 下的千次主键查找。
- 全表扫描风险:WHERE 中用了
UPPER(email)或DATE(created_at)→ 索引失效 - JOIN 必退化为 Block Nested-Loop Join,
join_buffer_size默认仅 256K,根本不够用 - 替代方案:把需要的数据从应用层查好、随 INSERT 一起传入,触发器只做
SET NEW.discount = ?
触发器不是 API 接口,它没缓存、不复用执行计划、不能异步——查表就是硬伤。
AFTER 触发器更新同表极易死锁
在 AFTER INSERT ON orders 里写 UPDATE order_summary SET total = total + NEW.amount,等于让同一事务反复抢 order_summary 的行锁,和别的事务一碰就死锁。
- MySQL 8.0+ 对锁行为更严格,5.7 上没报错的,8.0 直接
Deadlock found when trying to get lock - BEFORE 触发器也不能乱用:里面带
SELECT ... FOR UPDATE会延长主事务持锁时间 - 真正安全的做法:统计类逻辑一律移出触发器;必须保留则改用
FOR EACH STATEMENT+NEW TABLE(PostgreSQL)或应用层原子累加(如 Redis INCR)
触发器里更新目标表,本质是设计反模式——它混淆了“数据状态”和“业务动作”的边界。
复杂逻辑必须异步解耦
发邮件、写审计日志、调外部服务、JSON 解析、聚合计算……这些操作只要进触发器,就等于把延迟同步塞进主事务,卡住所有写入。
- 轻量队列表方案:触发器只做
INSERT INTO trigger_queue (table_name, row_id, event_type) VALUES ('orders', NEW.id, 'INSERT') - 外部消费者用
SELECT ... FOR UPDATE SKIP LOCKED LIMIT 100拉取,处理完再标为 done - SQL Server 必须用 Service Broker:
ALTER DATABASE [db] SET ENABLE_BROKER是前提,漏掉直接SEND报错
最常被忽略的点:触发器里哪怕只调用一个带 SELECT 的存储函数,MySQL 也会为每次调用开启子事务——1000 行插入,就是 1000 次隐式事务开销。










