sql触发器必须做轻量、确定、无副作用的同步响应,如写审计日志、更新统计字段;严禁跨库操作、同一表再更新、复杂校验及批量不兼容逻辑。

SQL触发器不是复杂业务逻辑的容器,而是数据变更的轻量守门员——它只该做一件事:在数据落库那一瞬间,完成确定、快速、无副作用的同步响应。
哪些操作必须用触发器,哪些绝对不该放进去
触发器适合做“数据一变,逻辑即响”的确定性动作,比如写审计日志、更新统计字段、派生只读计算列;但一旦出现以下任一情况,就该立刻停手,换存储过程或应用层处理:
-
SELECT COUNT(*)判断存在性 → 改用EXISTS,否则大表上直接拖慢整条 INSERT - 触发器里调用
UPDATE other_db.table→ MySQL 会报ERROR 1142,跨库写必须由应用承担 - 在
AFTER UPDATE中对同一张表再执行UPDATE→ MySQL 报ERROR 1442,这是引擎硬限制 - 用
NEW.status = NULL赋值给NOT NULL字段 → 在非 STRICT 模式下静默转为默认值,查不到错但逻辑已失效
BEFORE vs AFTER:时机选错,整个事务就卡住
选错触发时机不是功能问题,是稳定性问题。BEFORE 用于拦截或改写即将入库的数据(如标准化手机号、拒绝非法状态),AFTER 用于依赖已提交结果的动作(如写日志、更新汇总表)。关键区别在于:
-
BEFORE INSERT中可写SET NEW.created_at = NOW(),但不能读NEW.id(自增还没生成) -
AFTER INSERT中可安全读NEW.id,但不能再赋值给NEW.col,否则报ERROR 1362 - 需要校验“客户余额是否足够”这种跨表强一致性逻辑?别在触发器里
SELECT ... FOR UPDATE—— 极易死锁,应下沉到带事务的应用服务层
多行操作下,触发器逻辑必须兼容批量
开发时习惯单行测试,上线后 INSERT INTO t SELECT ... 一次插几千行,触发器若没考虑 ROW_COUNT() > 1,就会漏处理或只处理第一行。真实场景中:
- 不要在触发器里写
SELECT * FROM inserted LIMIT 1—— SQL Server 的inserted表、MySQL 的NEW都是逐行触发,但逻辑必须按行独立执行 - 避免在触发器内调用依赖会话变量的函数(如
USER()或自定义变量),因为批量操作中会话上下文不唯一 - SQLite 的
INSTEAD OF触发器可用来封装视图更新逻辑,但 MySQL/PostgreSQL 不支持对普通表的INSTEAD OF,别混淆语法
最常被忽略的点:触发器没有“调试入口”,出错时只能靠 SHOW TRIGGERS + 日志表 + 回滚事务反复试。哪怕只是加一行 INSERT INTO debug_log,也得确保它不拖慢主业务路径——否则线上抖动,没人能定位到底是哪条触发器在偷偷吃性能。











