触发器应仅做轻量级拦截,复杂业务逻辑须下沉至应用层或存储过程;禁用COUNT(*)>0改用EXISTS;禁止跨库更新、外部调用及变量滥用,确保事务安全与性能。

触发器里别直接写复杂业务逻辑
SQL触发器不是业务层,硬塞校验规则进去容易失控。比如在 BEFORE INSERT 里调用存储过程做多表联合校验、循环查余额、拼接字符串生成单号——这些操作会让触发器变慢、难调试、还可能引发死锁。
常见错误现象:ERROR 1442: Can't update table 'xxx' in stored function/trigger because it is already used by statement which invoked this stored function/trigger,本质是 MySQL 禁止在触发器里修改当前正在变更的表,但复杂逻辑常不自觉踩中这条线。
- 把核心校验逻辑下沉到存储过程或应用层,触发器只做轻量级拦截(如非空、范围检查)
- 若必须查其他表,确保只读且加
SELECT ... FOR UPDATE的场景极少,优先用缓存或冗余字段替代实时 JOIN - 避免在触发器里调用外部服务、写日志文件、发消息——这些不属于数据库职责,也破坏事务一致性
布尔判断别依赖 SELECT COUNT(*) > 0
用 COUNT(*) 判断存在性,是典型低效写法。它会扫描全表或索引直到统计完所有匹配行,而你只需要知道“有没有”。尤其在大表上,COUNT(*) > 0 和 EXISTS 性能差一个数量级。
使用场景:触发器里校验某客户是否已存在、某订单是否关联有效合同等。
- 改用
EXISTS (SELECT 1 FROM t WHERE ...),数据库遇到第一条匹配就返回 true,不继续扫 - 确保子查询里的条件字段有索引,否则
EXISTS也快不起来 - 不要写
IF (SELECT COUNT(*) FROM ...) > 0 THEN ...—— MySQL 不允许触发器中直接用标量子查询作条件表达式,会报ERROR 1356: View 'xxx' references invalid table(s) or column(s)
MySQL 触发器无法跨库更新,但可以跨库查询
MySQL 的触发器作用域严格限制在当前数据库内,UPDATE other_db.table_name SET ... 会直接报错 ERROR 1142: UPDATE command denied to user(即使权限足够),这是引擎层限制,不是权限问题。
但 SELECT 跨库是允许的,所以常见误操作是:想在 A 库的触发器里同步更新 B 库状态,结果发现写不进去,又不敢删触发器,只能临时加应用层补偿。
- 跨库写操作必须由应用层或定时任务承担,触发器只负责本库数据守门
- 如果真需要强一致联动,考虑合并库表结构,或用 MySQL 8.0+ 的原子 DDL + 外键约束替代部分触发器职责
- 测试时务必在真实多库环境下验证,开发机单库跑通不代表生产可用
触发器里的变量作用域和错误传播很脆弱
MySQL 触发器没有 TRY...CATCH,任何语句出错(比如除零、类型转换失败、子查询返回多行)都会中断整个触发器,并回滚原 SQL。更麻烦的是,局部变量(DECLARE v_flag INT DEFAULT 0)在不同事件类型(INSERT/UPDATE)中初始化行为不一致,容易漏设默认值。
常见错误现象:ERROR 1242: Subquery returns more than 1 row,出现在 SET v_x = (SELECT id FROM t WHERE ...) 中,但没加 LIMIT 1 或没做唯一性约束。
- 所有
SELECT ... INTO必须确保最多一行返回,加LIMIT 1或提前用EXISTS过滤 - 布尔判断尽量用
IF EXISTS(...)或IF (SELECT 1 FROM ... LIMIT 1) IS NOT NULL,避开变量赋值环节 - 不要依赖未声明的会话变量(如
@var),它们在并发触发下可能被覆盖,且不保证初始化

















