MySQL插入冲突的两种处理方式

 更新时间:2026年08月25日 09:10:05   作者:Nontee22  
在MySQL中遇到唯一索引冲突不用慌,本文详细对比INSERT IGNORE和ON DUPLICATE KEY UPDATE两种处理重复数据的语法,教你如何根据业务场景选择最合适的方案,避开先查后插的并发陷阱,实现安全高效的幂等写入和更新操作,需要的朋友可以参考下

从一个报错说起

给表加了唯一索引之后,数据库就开始帮你挡重复数据。但挡归挡,问题是它挡人的方式很直接——INSERT 执行到一半撞上冲突,直接抛错:

ERROR 1062 (23000): Duplicate entry 'test@example.com' for key 'uk_email'

刚入门时我的第一反应是:插入前先查一遍。SELECT 一下这条 email 存不存在,不存在再 INSERT。单线程跑没问题,但只要两个请求并发进来,就可能同时通过 SELECT 检查、再同时 INSERT——还是炸。先查后插本质上是把检查和写入拆成了两步,中间的空隙就是隐患。

其实 MySQL 自己就提供了两种"撞上冲突怎么办"的语法,区别只在于:冲突发生的那一刻,你希望数据库做什么?

  • 什么都不做,跳过这一行 → INSERT IGNORE
  • 更新一下已有的那行 → ON DUPLICATE KEY UPDATE

下面用一个例子把两种都过一遍。

准备:一张会冲突的表

CREATE TABLE users (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    email VARCHAR(64) NOT NULL,
    name VARCHAR(64) NOT NULL,
    last_login DATETIME,
    UNIQUE KEY uk_email (email)
);

email 上建了唯一索引(顺带一提:MySQL 的唯一索引允许多个 NULL 共存,所以这里 email 要 NOT NULL,否则"空值"能无限重复插入)。

INSERT IGNORE:撞了就跳过

机制:正常尝试插入,如果因为唯一索引冲突失败,就静默跳过这一行——不报错、不插入,只是受影响行数为 0。

-- 如果 email 'test@example.com' 已存在,这条语句静默跳过,无任何报错
INSERT IGNORE INTO users (email, name) VALUES ('test@example.com', 'John');

关键点:必须检查受影响行数。 这是判断"到底插没插进去"的唯一方式。用 Java 写就是看 update() 的返回值:

int rows = jdbcTemplate.update(
    "INSERT IGNORE INTO users (email, name) VALUES (?, ?)",
    "test@example.com", "John");
if (rows == 0) {
    // 冲突了:email 已存在。记日志或走别的分支
    log.warn("email 已存在,跳过插入");
}

平时写 CRUD 很少有人看 executeUpdate() 的返回值,用了 INSERT IGNORE 之后它就是关键情报:1 表示插入成功,0 表示撞了冲突。

一个容易踩的坑IGNORE 忽略的不只是重复键错误。字段超长被截断、类型不匹配这类本来会报错的情况,也会被降级成警告静默放行。所以它比名字听起来更"宽容",数据质量要求高的场景要想清楚再用。

适用场景:幂等写入——批量导入去重、初始化数据、日志记录。这类场景的特点是"重复了就算了,我不需要知道细节"。

ON DUPLICATE KEY UPDATE:撞了就更新(最推荐)

机制:先尝试插入,如果冲突,就转去 UPDATE 已有的那一行。更新哪些字段完全由你指定——所以这种方式也叫 Upsert(存在就更新,不存在就插入)。

-- email 存在则更新 name 和 last_login;不存在则插入新记录
INSERT INTO users (email, name, last_login)
VALUES ('test@example.com', 'John', NOW())
ON DUPLICATE KEY UPDATE name = VALUES(name), last_login = VALUES(last_login);

VALUES(name) 是个便捷函数,代表这条 INSERT 语句里 name 列的值,避免把同一个值写两遍。

受影响行数:1 表示执行了插入,2 表示执行了更新。有个细节要知道——如果更新后的值和原来一模一样,受影响行数是 0。所以拿返回值判断操作类型时,别漏了这种情况。

一个 VALUES() 覆盖不了的场景——自增计数

-- 每次执行浏览量 +1:不存在则插入并置 1,存在则 +1
INSERT INTO article_stats (article_id, view_count)
VALUES (1001, 1)
ON DUPLICATE KEY UPDATE view_count = view_count + 1;

view_count = view_count + 1 引用的是表里已有的旧值,这是 VALUES() 做不到的,计数器类需求只能这么写。

想要"完全覆盖旧数据"怎么办? 不需要什么特殊语法——把所有字段都写进 UPDATE 子句就行:

INSERT INTO users (email, name, last_login)
VALUES ('test@example.com', 'Jane', NOW())
ON DUPLICATE KEY UPDATE name = VALUES(name), last_login = VALUES(last_login);

这样旧行除了唯一键和主键之外的字段全部被新值覆盖,效果就是"替换",而且主键 id 不变、其他表的外键引用不受影响。比物理删除再插入的做法可控得多。

版本提醒:MySQL 8.0.20 起官方把 VALUES(col) 标记为废弃,推荐用行别名写法:

INSERT INTO users (email, name, last_login)
VALUES ('test@example.com', 'John', NOW()) AS new
ON DUPLICATE KEY UPDATE name = new.name, last_login = new.last_login;

两种写法目前都能跑,老项目里 VALUES() 还随处可见,看新教程时别被两套写法搞懵。

适用场景:用户登录刷新时间、库存计数、配置项更新、覆盖式写入——凡是"有则更新、无则插入"的需求,首选它。

两种方案对比

方案核心机制冲突时行为受影响行数典型场景关键注意事项
INSERT IGNORE尝试插入,静默跳过冲突行不插入、不报错0(冲突)/ 1(成功)幂等写入、批量导入去重只能靠行数判断结果;其他错误也会被静默
ON DUPLICATE KEY UPDATE尝试插入,更新冲突行指定字段更新指定字段,保留其他字段1(插入)/ 2(更新)Upsert:刷新时间、计数器、覆盖写入需显式指定更新字段,灵活性最高

怎么选:决策指南

拿业务需求对着这张决策树走一遍,基本就能定:

插入撞上唯一索引冲突,你希望数据库做什么?
│
├─ 冲突了就算了,什么都不用做
│   └─ INSERT IGNORE(批量导入、初始化数据去重)
│
└─ 冲突了要更新数据(部分字段或全部覆盖)
    └─ ON DUPLICATE KEY UPDATE(绝大多数 Upsert 场景,首选)

两条核心原则:

  1. 首选 ON DUPLICATE KEY UPDATE:精确控制更新哪些字段,从"只更新一个时间戳"到"覆盖所有字段"都能胜任,覆盖绝大多数业务场景。
  2. 需要"保证不重复"而不需要更新时,用 INSERT IGNORE:它最简单,但代价是静默——记得检查受影响行数,别让冲突悄悄溜过去。

写在最后

整理完这两种语法,有几点体会:

  1. 受影响行数是个宝。写 CRUD 的时候 update() 返回值从来没人看,但这两种语法全靠它反馈结果。数据库给了你返回值,就看你要不要用。
  2. 唯一索引才是真正挡重复的那道门。两种语法都靠唯一索引触发——没有唯一索引,INSERT IGNORE 只是普通插入,ON DUPLICATE KEY UPDATE 的 UPDATE 分支永远不会走到。先想清楚"什么算重复"(哪个字段建唯一索引),再想"冲突了怎么办"(选哪种语法),顺序不能反。
  3. 先查后插不是不行,是要加锁或者靠唯一索引兜底。并发场景下,"检查+写入"必须是一个原子操作才算安全。这两种语法本质上是把冲突处理下沉到数据库层,让存储引擎替你做原子性保证——这也是我不再执着于先 SELECT 再 INSERT 的原因。

以上就是MySQL插入冲突的两种处理方式的详细内容,更多关于MySQL插入冲突处理的资料请关注脚本之家其它相关文章!

相关文章

  • 详解MySQL性能优化(二)

    详解MySQL性能优化(二)

    本文对MySQL性能优化进行了详细的总结与介绍,需要的朋友可以参考下
    2015-08-08
  • mySQL中in查询与exists查询的区别小结

    mySQL中in查询与exists查询的区别小结

    最近被一个朋友问到mySQL中in查询和exists的区别,当然只是草草的回答了下,今天偶然看到了一篇关于mysql中的exists查询的文章,读完感觉太”冷落”它了,这里总结一下,也跟自己常用的in查询做一下对比。有需要的朋友们可以参考借鉴,下面来一起学习学习吧。
    2016-11-11
  • MySQL表的增删查改操作实例代码

    MySQL表的增删查改操作实例代码

    MySQL表的增删查改(CRUD)是数据库中非常基础的部分,也是后端开发日常工作中,最重要的一项工作,这篇文章主要介绍了MySQL表的增删查改操作的相关资料,需要的朋友可以参考下
    2025-07-07
  • MySQL中count(*)执行慢的解决方案

    MySQL中count(*)执行慢的解决方案

    这篇文章主要介绍了MySQL中count(*)执行慢的解决方案,文章围绕主题展开详细的内容介绍,具有一定的参考价值,需要的小伙伴可以参考一下
    2022-06-06
  • 一文详解MySQL的IP地址如何在数据库里存储

    一文详解MySQL的IP地址如何在数据库里存储

    存储 IP 地址是系统开发中的常见需求,主要用途包括:安全审计与防护,用户分析与画像,网络与设备管理,日志分析与追踪,本文通过代码示例给大家介绍了MySQL的IP地址如何在数据库里存储,需要的朋友可以参考下
    2026-07-07
  • phpstudy安装后mysql无法启动的解决

    phpstudy安装后mysql无法启动的解决

    本文主要介绍了phpstudy安装后mysql无法启动的解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-03-03
  • MySQL查询结果处理方式

    MySQL查询结果处理方式

    这篇文章主要介绍了MySQL查询结果处理方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-04-04
  • MySQL单表存多大的数据量比较合适

    MySQL单表存多大的数据量比较合适

    MySQL数据库在处理大规模数据时,性能会受到影响,这时候就需要考虑分库分表策略,本文就来介绍一下MySQL单表存多大的数据量比较合适,感兴趣的可以了解一下
    2024-11-11
  • mysql数据库卡顿问题排查过程

    mysql数据库卡顿问题排查过程

    介绍了四种排查数据库问题的方法,包括查看SQL运行情况、库和表信息、数据库配置情况以及重启数据库,每种方法都有具体的操作步骤和注意事项,旨在帮助读者解决数据库资源不足、死锁等问题
    2025-02-02
  • 解析MYSQL显示表信息的方法

    解析MYSQL显示表信息的方法

    本篇文章是对MYSQL显示表信息的方法进行了详细的分析介绍,需要的朋友参考下
    2013-06-06

最新评论