根本原因是重试前未做幂等判断,导致每次重试都被当作新请求执行INSERT;必须通过唯一索引+INSERT ... ON DUPLICATE KEY UPDATE原子化处理,并全程传递幂等键。

事务重试时为什么反而写入更多数据?
根本原因不是重试本身,而是重试前没做幂等判断,导致每次重试都当作新请求执行 INSERT。比如一个支付回调接口,上游因超时重发三次,后端没识别这是同一笔订单,就插入了三条记录——这不是数据库问题,是业务逻辑漏判。
用 INSERT ... ON DUPLICATE KEY UPDATE 替代裸重试
这是最常用也最稳妥的方案,前提是目标字段有唯一索引(如 order_id、trade_no)。它把“查+插/更新”原子化交给 MySQL 做,避免应用层竞态。
- 必须先建唯一索引,否则语句不生效:
ALTER TABLE payments ADD UNIQUE (trade_no); - 不要只更新时间戳,要确保关键字段可被覆盖,比如:
INSERT INTO payments (trade_no, amount, status) VALUES ('T123', 99.9, 'success') ON DUPLICATE KEY UPDATE status = VALUES(status), updated_at = NOW(); - 注意
VALUES()函数引用的是本次 INSERT 的值,不是当前行旧值 - 如果业务不允许覆盖(比如状态只能从 pending → success,不能倒退),就得在
UPDATE子句里加条件:ON DUPLICATE KEY UPDATE status = IF(status = 'pending', VALUES(status), status)
INSERT IGNORE 的适用边界和风险
INSERT IGNORE 看似简单,但容易掩盖真实问题:它对所有约束冲突(主键、唯一索引、外键)都静默跳过,连本该报错的非法数据也会吞掉。
- 仅适合“纯去重”场景,比如日志表、埋点表,且你确认重复只是冗余,不是数据错误
- 不会返回影响行数(
mysql_affected_rows()返回 0),无法区分“真插入”和“被忽略” - 与
ON DUPLICATE KEY UPDATE不兼容——两者语法互斥,不能混用 - 在严格模式下,某些警告会升级为错误,导致语句失败,这点常被忽略
重试逻辑里必须带业务标识和幂等键
哪怕用了数据库层去重,应用层仍要生成并传递幂等键(如 idempotency_key),否则重试请求无法被关联识别。
- 幂等键应由调用方生成(如客户端 UUID 或服务端基于参数哈希),不能由数据库自增
- 建议存入数据库额外字段(如
idempotency_key VARCHAR(64)),便于排查和审计 - 重试时必须原样携带该键,不能每次重试都生成新键
- 若使用消息队列,需确保消费位点提交晚于写库成功,否则消息重投会触发二次写入
真正难的不是选哪个 SQL 语句,而是把幂等键贯穿整个链路——从 API 入口、MQ 生产、事务边界到最终落库,漏掉一环,重试就变成放大器。


















