当前位置: 代码网 > it编程>数据库>Mysql > MySQL插入冲突的两种处理方式

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

2026年08月25日 Mysql 我要评论
从一个报错说起给表加了唯一索引之后,数据库就开始帮你挡重复数据。但挡归挡,问题是它挡人的方式很直接——insert 执行到一半撞上冲突,直接抛错:error 1062 (230

从一个报错说起

给表加了唯一索引之后,数据库就开始帮你挡重复数据。但挡归挡,问题是它挡人的方式很直接——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插入冲突处理的资料请关注代码网其它相关文章!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2026  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com