SQL 触发器常见误用与优化案例

1次阅读

mysql触发器中select查不到本事务刚插入的数据,是mvcc正常行为;应使用new/old引用字段,跨行校验需用after触发器+显式事务;on duplicate key update仅触发update类触发器;避免触发器调用写表存储过程以防死锁;审计日志触发器须建索引并禁用拼接操作。

SQL 触发器常见误用与优化案例

触发器里写 SELECT 还没提交就查到了脏数据?

MySQL 的 BEFORE 触发器里执行 SELECT,查的其实是事务当前快照里的旧值——哪怕你刚在同个事务里 INSERT 了一条记录,SELECT 也看不到它。这不是 bug,是 MVCC 的正常行为。

  • 想查刚插入/更新的数据,得用触发器参数:NEW.col_nameOLD.col_name,别去查表
  • 如果真要跨行校验(比如检查用户名是否重复),必须用 AFTER 触发器 + 显式事务控制,否则可能漏判
  • postgresql 没这问题,它的 BEFORE 触发器能看到本事务已做的变更,但 MySQL 不行

INSERT ... ON DUPLICATE KEY UPDATE 和触发器打架怎么办?

当语句命中唯一键冲突时,MySQL 会走 UPDATE 分支,但只触发 BEFORE UPDATEAFTER UPDATE,完全跳过 INSERT 类触发器。很多人误以为“既然写了 INSERT 就该进 INSERT 触发器”,结果逻辑断层。

  • 把核心校验或生成逻辑从 BEFORE INSERT 搬到 BEFORE UPDATE,或者两个都写一遍
  • 避免在触发器里依赖“这是 INSERT 还是 UPDATE”的判断,改用 if NEW.id = OLD.id THEN ... 这类实际字段比对
  • 注意 ON DUPLICATE KEY UPDATE 中的 VALUES(col) 函数返回的是原始 INSERT 值,不是最终写入值,别拿它在触发器里做状态推导

触发器调用存储过程导致死锁?

触发器里调用含 UPDATEINSERT 的存储过程,极易引发死锁,尤其在高并发批量写入时。因为触发器运行在原 SQL 的事务上下文中,锁粒度和顺序全被带进去了。

  • 优先把逻辑平铺进触发器体,去掉存储过程调用;如果必须复用,确保该过程只读、不碰任何用户表
  • 避免在触发器里调用另一个会修改同张表的存储过程——等于自己锁自己
  • MySQL 8.0+ 可用 GET_LOCK() 做轻量级互斥,但别在触发器里用,会放大阻塞

审计日志触发器为什么越跑越慢?

给每张业务表配一个 AFTER INSERT/UPDATE/delete 触发器,往 audit_log 表里插记录,初期没问题,半年后单次插入延迟从 2ms 涨到 200ms,根本原因是日志表没索引 + 触发器没批处理。

  • audit_log 至少建复合索引:(table_name, op_time),否则 SELECT count(*) 类统计查询会拖垮整个触发器链
  • 别在触发器里做 CONCAT 拼大 json 字段,字符串操作在行级触发器里开销极大;用 JSON_OBJECT() 更稳
  • 真正大批量操作(如迁移、补数)前,临时 DROP TRIGGER,事后补日志,别硬扛

触发器不是黑盒魔法,它和主 SQL 共享事务、锁、执行计划——所有你以为“它在后台悄悄干”的事,其实都卡在你当前这条语句的路径上。

text=ZqhQzanResources