应优先创建唯一索引拦截重复插入,仅当涉及多表状态、时间窗口等复杂逻辑时才用BEFORE INSERT触发器配合SIGNAL报错兜底。

MySQL 触发器里怎么判断重复插入
直接看 BEFORE INSERT 触发器里的校验逻辑:不能靠查表后 RETURN 或 ROLLBACK(触发器里不支持),得用 SIGNAL 主动报错中断。常见错误是写成 SELECT COUNT(*) + IF 然后啥也不做,结果插入照常发生。
实操建议:
- 在
BEFORE INSERT中用SELECT ... INTO @var查唯一约束字段(比如order_id、tx_hash)是否存在,再用IF @var IS NOT NULL THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'duplicate submit'; END IF; - 确保被查字段上有索引,否则每次插入都全表扫描,性能崩得比业务还快
- 别在校验逻辑里更新其他表——触发器嵌套太深容易锁表或死锁
PostgreSQL 的 ON CONFLICT DO NOTHING 能替代触发器吗
能,而且更轻量、更可靠。但只适用于「插入时冲突就丢弃」的场景,不适用于需要返回明确错误码、记录日志、或执行补偿动作的情况。
关键差异点:
-
ON CONFLICT依赖已有唯一索引或主键,没建索引会直接报错there is no unique or exclusion constraint - 它不进触发器链,所以
BEFORE/AFTER INSERT触发器在冲突被忽略时根本不会执行 - 如果业务要区分「新增成功」和「因幂等被忽略」,得靠
RETURNING配合判断:没返回行说明被忽略了
触发器做幂等校验时最容易锁表的地方
不是 SELECT,而是你没意识到的隐式锁。比如在 MySQL 中,SELECT ... FOR UPDATE 或 INSERT ... SELECT ... FOR UPDATE 在触发器里一用,立刻升级成行锁甚至间隙锁,高并发下排队堵死。
避坑方式:
- 绝对不要在触发器里写
SELECT ... FOR UPDATE—— 校验只需读,不需要加锁 - 如果必须基于最新状态决策(比如余额校验),优先考虑把校验提到应用层 + 分布式锁,而不是塞进触发器
- MySQL 8.0+ 可以用
SELECT ... LOCK IN SHARE MODE降级,但依然有锁开销,不如索引+唯一约束硬控制
为什么推荐先建唯一索引再考虑触发器
因为 95% 的重复提交问题,靠 UNIQUE KEY (user_id, order_no) 就能拦住,根本轮不到触发器出手。触发器是兜底,不是主力。
实际落地顺序应该是:
- 先分析重复依据字段(比如
user_id + request_id),建联合唯一索引 - 应用层捕获
1062 Duplicate entry错误,转成业务可读提示 - 只有当重复逻辑涉及多表状态、时间窗口判断、或需调用外部服务时,才用触发器补足
越想用触发器“一把梭”解决所有幂等问题,越容易在事务边界、隔离级别、主从延迟上栽跟头

















