触发器无法处理唯一性冲突,因其在约束检查前执行且无法干预duplicate entry错误;高并发防重必须依赖唯一索引配合on duplicate key update等原生命令。

触发器不能处理唯一性冲突,这是根本前提
SQL触发器在唯一键冲突发生前就已执行完毕,它无法捕获或干预 Duplicate entry 错误。当你执行 INSERT 时,MySQL 先走触发器逻辑(BEFORE 或 AFTER),再做约束检查;一旦违反 UNIQUE 或 PRIMARY KEY,错误在触发器之后才抛出,触发器对此无感知、无控制权。
常见误解是:在 BEFORE INSERT 里查一遍是否存在,再决定是否 SIGNAL。这看似防重,实则无效——因为两个并发请求几乎同时查到“不存在”,然后都进入插入阶段,最终仍触发唯一索引冲突。这不是概率问题,是必然竞态。
为什么非要用触发器“处理”冲突是个伪需求
真正需要的不是“在触发器里兜底”,而是“让冲突不发生,或让冲突有确定性结果”。高并发下唯一性保障必须交给数据库原生机制,而非应用层或触发器模拟:
-
ON DUPLICATE KEY UPDATE是最直接解法,但前提是表上已有对应字段的UNIQUE或PRIMARY KEY约束 -
INSERT IGNORE适合只关心写入成功与否、不关心状态变更的场景(如埋点、日志) -
REPLACE INTO实质是DELETE + INSERT,会改变自增 ID、触发DELETE相关触发器、破坏外键引用,生产环境慎用 - 所有这三者都要求:冲突字段必须有唯一索引支撑,否则语句静默失效
如果业务硬要“在插入前后做点事”,触发器该怎么配
触发器唯一合理的角色,是配合唯一约束做轻量副作用,比如自动填充时间戳、生成审计字段、更新关联计数器(需确保计数器表已建好索引)。但它绝不能承担去重逻辑本身:
- 在
BEFORE INSERT中,仅允许对NEW字段做简单赋值(如NEW.created_at = NOW()),禁止SELECT ... INTO或调用含查询的存储过程 - 避免在触发器中更新其他业务主表,尤其不要更新被唯一约束保护的字段(如
NEW.order_no),否则可能引发嵌套冲突或ERROR 1442 - 若需记录操作日志,日志表必须独立部署、不与主表共用事务上下文,否则极易因锁等待导致循环阻塞
- 批量导入时(如
LOAD DATA INFILE),应临时禁用触发器:ALTER TABLE t DISABLE TRIGGER tr_name,处理完再启用
真正该盯住的死锁和性能陷阱
高并发下多个事务争抢同一唯一键值,InnoDB 会升级为 Next-Key Lock,形成 S 锁等待 X 锁的死锁链。这不是代码写得不够好,而是锁机制使然:
- 监控
Innodb_row_lock_waits和SHOW ENGINE INNODB STATUS中的deadlock片段,确认是否由唯一键冲突引发 - 组合唯一索引要谨慎,若
INSERT只提供前缀列(如索引是(a,b)但只写a=1),间隙锁范围会扩大,加剧争抢 -
ON DUPLICATE KEY UPDATE的SET子句里禁止写子查询或函数调用(如updated_at = NOW() + INTERVAL 1 DAY),否则延长锁持有时间 - 如果冲突频发且不可控,说明业务设计有问题——比如用时间戳+随机数拼订单号,在毫秒级并发下天然撞车,应改用
UUID()或 Snowflake ID
唯一性不是靠触发器“补救”出来的,是靠索引定义、语句选型、ID 生成策略三层共同守住的。把触发器当防重主力,就像用创可贴堵水管爆裂口——表面贴住了,底下早漏成河。










