mysql触发器禁止对正在操作的表执行insert/update/delete,否则报error 1442;禁止在触发器中用select max(id)+1生成id,易致并发重复;日志表主键勿依赖now()或connection_id(),应使用auto_increment或uuid;树形表中不可在触发器内修改主键或外键字段,以防error 1452。

触发器里 INSERT 同表直接报错 ERROR 1442
MySQL 明确禁止在触发器中对“当前正在被语句操作的表”再做 INSERT/UPDATE/DELETE,否则会抛 ERROR 1442 (HY000)。很多人以为只在 AFTER INSERT 里才触发,其实 BEFORE INSERT 中只要逻辑上导致对同一张表的写入(比如调用含 INSERT 的存储过程),同样会卡住。
常见错误写法:
- 在
BEFORE INSERT ON orders里执行INSERT INTO log_table—— 没问题;但如果log_table上也有触发器,且该触发器又去改orders,就构成隐式嵌套写入 - 触发器内调用存储过程,而该过程包含
INSERT INTO orders—— MySQL 把整个调用链视为“同一上下文”,照样报 1442
解决思路:把写日志、发通知等副作用操作移出触发器,改用应用层异步处理,或用 INSERT ... SELECT + 临时表绕过限制。
触发器中用 SELECT MAX(id)+1 生成 ID 导致并发重复
在 BEFORE INSERT 里手动设 NEW.id = (SELECT MAX(id) FROM t) + 1 是高危操作。两个并发事务几乎同时查到相同 MAX(id),都会+1后插入,必然主键冲突。
正确做法只有两种:
- 用
AUTO_INCREMENT—— 简单、可靠、MySQL 原生支持并发安全 - 用
UUID()或UUID_SHORT()—— 不依赖表状态,但索引效率略低;若业务强依赖数字 ID,可用单独的sequence表配合SELECT ... FOR UPDATE控制
绝对不要在触发器里做任何需要“先读再算再写”的逻辑,这类操作天然不满足原子性。
多个触发器争抢同一张日志表引发主键冲突
当多个触发器(或触发器+应用代码)都往一张日志表插数据,且日志表主键依赖 NOW() 或 CONNECTION_ID(),在毫秒级并发下极易撞车。例如:
CREATE TABLE log ( id BIGINT PRIMARY KEY DEFAULT (UNIX_TIMESTAMP(NOW(3)) * 1000 + CONNECTION_ID()), msg TEXT );
这个 id 在同一毫秒内多个连接会重复,导致插入失败甚至事务回滚。
更稳妥的日志表设计:
- 主键用
AUTO_INCREMENT,不参与业务逻辑 - 加唯一组合索引覆盖业务维度,如
(biz_type, biz_id, created_at),靠数据库约束防重,而非靠 ID 唯一 - 如果真要全局唯一标识,用
UUID()作为业务字段,不作主键
触发器更新自身表的外键字段引发 ERROR 1452
在树形结构表(如带 parent_id 的分类表)上,BEFORE UPDATE 里修改 NEW.id 或 NEW.parent_id 极易触发 ERROR 1452。MySQL 外键校验发生在语句执行末尾,此时原 id 已变、新 parent_id 又找不到对应行,约束直接失败。
根本原则:主键和外键引用字段一旦写入,就应视为不可变。业务上需要“移动节点”,正确做法是:
- 用软删除标记旧路径,再 INSERT 新节点
- 用闭包表(closure table)管理层级关系,避免直接改
id/parent_id - 禁用
ON UPDATE CASCADE,这种级联在触发器内行为不可控
触发器不是万能胶,它适合轻量、确定、无副作用的状态同步;一旦涉及跨表约束、ID 生成、并发计数,就该交给应用层或专用服务来兜底。











