mysql upsert 用法(批量保存或更新)
学习链接
MySQL中ON DUPLICATE KEY UPDATE的介绍与使用、批量更新、存在即更新不存在则插入
MySQL中replace into详解、批量更新、不存在插入存在则更新、replace into的坑
文章目录
- 学习链接
- on duplicate key update 用法
- 基本语法
- 工作原理
- 使用示例
- 示例1:基本用法
- 示例2:使用 values(列名) 函数引用 原插入到该列 的 值
- 示例3:多行插入或更新(批量)
- 示例4:插入或按条件更新
- 注意事项
- 实际应用场景
- 代码示例
- insert插入,默认不返回主键
- insert插入,指定返回主键
- upsert语法初体验
- 基础情况1:唯一键冲突,列值未作实际修改,返回的结果是1(也可以是0)
- 基础情况2:未有唯一键冲突,实际插入1条数据,返回的结果为1(只会是1)
- 基础情况3:唯一键冲突,任意列值作了实际修改,返回的结果是2(只会是2)
- 插入或按条件更新
- 多行插入或更新 (批量)
- MySQL中ON DUPLICATE KEY UPDATE的介绍与使用、批量更新、存在即更新不存在则插入
- 一、ON DUPLICATE KEY UPDATE的介绍
- 二、ON DUPLICATE KEY UPDATE的使用
- 2.1、案例一:根据主键id进行更新
- 2.2、案例二:根据唯一索引进行更新(常用)
- 2.3、案例三:没有主键或唯一键字段值相同就插入
- 2.4、案例四:主键与唯一键字段同时存在
- 三、ON DUPLICATE KEY UPDATE的注意事项
- 3.1、on dupdate key update之后values的使用事项
- 3.2、对values使用判断
- 3.3、唯一索引大小写敏感问题
- 四、ON DUPLICATE KEY UPDATE与mybatis联合使用
- 4.1、写法一:与values()联合使用
- 4.2、写法二:使用#{}
- 五、ON DUPLICATE KEY UPDATE的缺点及坑
on duplicate key update 用法
on duplicate key update 是 MySQL 中 INSERT 语句的一个非常有用的扩展,它允许你在插入数据时,如果遇到主键或唯一键冲突,就执行更新操作而不是报错。
基本语法
INSERT INTO table_name (column1, column2, ...)
VALUES (value1, value2, ...)
ON DUPLICATE KEY UPDATE column1 = value1, column2 = value2, ...;
工作原理
- 首先尝试执行普通的 INSERT 操作
- 如果插入成功,则正常插入数据
- 如果因为
主键或唯一键冲突导致插入失败,则转而执行 UPDATE 操作,更新指定的列
使用示例
示例1:基本用法
INSERT INTO users (id, name, email, login_count)
VALUES (1, 'John', 'john@example.com', 1)
ON DUPLICATE KEY UPDATE name = 'John', email = 'john@example.com', login_count = login_count + 1;
如果 id=1 的用户已存在,则更新其 name、email 并将 login_count 加1。
示例2:使用 values(列名) 函数引用 原插入到该列 的 值
INSERT INTO products (product_id, name, stock)
VALUES (100, 'Laptop', 10)
ON DUPLICATE KEY UPDATE stock = stock + VALUES(stock);
如果 product_id=100 的产品已存在,则将其库存增加10。
示例3:多行插入或更新(批量)
INSERT INTO students (student_id, name, score)
VALUES -- 此处在mybatis中可以使用foreach循环(1, 'Alice', 90),(2, 'Bob', 85),(3, 'Charlie', 92)
ON DUPLICATE KEY UPDATE name = VALUES(name), score = VALUES(score);
示例4:插入或按条件更新
drop table if exists tbl_biz_template;
create table tbl_biz_template (id int(10) primary key auto_increment,tmpl_biz_type varchar(10),tmpl_no varchar(10),effective_date datetime,tmpl_version varchar(10),audit_time datetime,enable_inapp_link varchar(10),-- 唯一键unique key uk(tmpl_biz_type, tmpl_no, effective_date)
);
select * from tbl_biz_template;-- 按所示顺序执行如下语句-- 返回 Affected rows: 1 插入了数据
insert into tbl_biz_template (id, tmpl_biz_type, tmpl_no, effective_date, tmpl_version, audit_time, enable_inapp_link)
values (null, 'type01', 'no01', '2025-11-30', 'version_01', '2025-10-25', '0110')
on duplicate key update
tmpl_version = values(tmpl_version),
audit_time = values(audit_time),
enable_inapp_link = values(enable_inapp_link);-- 返回 Affected rows: 0 没有插入,也没有更新
insert into tbl_biz_template (id, tmpl_biz_type, tmpl_no, effective_date, tmpl_version, audit_time, enable_inapp_link)
values (null, 'type01', 'no01', '2025-11-30', 'version_01', '2025-10-25', '0110')
on duplicate key update
tmpl_version = values(tmpl_version),
audit_time = values(audit_time),
enable_inapp_link = values(enable_inapp_link);-- 返回 Affected rows: 2 没有插入,有更新
insert into tbl_biz_template (id, tmpl_biz_type, tmpl_no, effective_date, tmpl_version, audit_time, enable_inapp_link)
values (null, 'type01', 'no01', '2025-11-30', 'version_02', '2099-11-26', '1111') on duplicate key update
tmpl_version = if(audit_time < values(audit_time) or audit_time is null, values(tmpl_version), tmpl_version),
audit_time = if(audit_time < values(audit_time) or audit_time is null, values(audit_time), audit_time),
enable_inapp_link = if(audit_time < values(audit_time) or audit_time is null, values(enable_inapp_link), enable_inapp_link)-- 返回 Affected rows: 0 没有插入,没有更新
insert into tbl_biz_template (id, tmpl_biz_type, tmpl_no, effective_date, tmpl_version, audit_time, enable_inapp_link)
values (null, 'type01', 'no01', '2025-11-30', 'version_02', '2099-11-26', '1111') on duplicate key update
tmpl_version = if(audit_time < values(audit_time) or audit_time is null, values(tmpl_version), tmpl_version),
audit_time = if(audit_time < values(audit_time) or audit_time is null, values(audit_time), audit_time),
enable_inapp_link = if(audit_time < values(audit_time) or audit_time is null, values(enable_inapp_link), enable_inapp_link)
MySQL官方文档对于INSERT … ON DUPLICATE KEY UPDATE的说明:
- 如果行作为新记录被插入,则受影响的行数为1。
- 如果现有记录被更新,则受影响的行数为2。
- 如果现有记录被更新,并且更新的值与原有值相同,则受影响的行数为0。
注意事项
- 只有当发生主键或唯一键冲突时才会触发 UPDATE
- 可以使用 VALUES(column_name) 函数
引用原本要插入的值 - 如果没有指定要更新的列,则该列的值保持不变
- 此操作是原子性的,要么全部成功,要么全部失败
- 对于自增主键,即使执行 UPDATE 操作,自增计数器也会增加
实际应用场景
- 计数器更新(如文章阅读数、用户登录次数)
- 数据去重插入
- 数据同步(存在则更新,不存在则插入)
- 缓存表更新
这个功能在需要"存在则更新,不存在则插入"的场景中非常有用,可以避免先查询再决定是插入还是更新的繁琐操作。
代码示例
建表sql,设置了主键id,联合唯一主键
drop table if exists tbl_biz_template;
create table tbl_biz_template (id int(10) primary key auto_increment,tmpl_biz_type varchar(10),tmpl_no varchar(10),effective_date datetime,tmpl_version varchar(10),audit_time datetime,enable_inapp_link varchar(10),-- 唯一键unique key uk(tmpl_biz_type, tmpl_no, effective_date)
);
select * from tbl_biz_template;
insert插入,默认不返回主键
public interface BizTemplateMapper extends BaseMapper<BizTemplateEntity> {int store1(@Param("entity") BizTemplateEntity entity);
}
<insert id="store1">insert into tbl_biz_template(tmpl_biz_type,tmpl_no,effective_date,tmpl_version,audit_time,enable_inapp_link)values (#{entity.tmplBizType},#{entity.tmplNo},#{entity.effectiveDate},#{entity.tmplVersion},#{entity.auditTime},#{entity.enableInappLink})
</insert>
BizTemplateEntity entity = new BizTemplateEntity();
entity.setTmplBizType("type01");
entity.setTmplNo("no01");
entity.setEffectiveDate(LocalDateTime.of(2025, 11, 30, 0, 0, 0));
entity.setTmplVersion("version_01");
entity.setAuditTime(LocalDateTime.of(2025, 10, 25, 0, 0, 0));
entity.setEnableInappLink("0110");// 若当前tbl_biz_template中没有数据,但是自增值到了5,那么执行这行代码后,返回的result_1为1,id为null
int result_1 = bizTemplateMapper.store1(entity);System.out.println("第一次插入的结果: " + result_1);
System.out.println("id: " + entity.getId());
insert插入,指定返回主键
public interface BizTemplateMapper extends BaseMapper<BizTemplateEntity> {int store2(@Param("entity") BizTemplateEntity entity);
}
<insert id="store2" useGeneratedKeys="true" keyProperty="id">insert into tbl_biz_template(tmpl_biz_type,tmpl_no,effective_date,tmpl_version,audit_time,enable_inapp_link)values (#{entity.tmplBizType},#{entity.tmplNo},#{entity.effectiveDate},#{entity.tmplVersion},#{entity.auditTime},#{entity.enableInappLink})
</insert>
BizTemplateEntity entity = new BizTemplateEntity();
entity.setTmplBizType("type01");
entity.setTmplNo("no01");
entity.setEffectiveDate(LocalDateTime.of(2025, 11, 30, 0, 0, 0));
entity.setTmplVersion("version_01");
entity.setAuditTime(LocalDateTime.of(2025, 10, 25, 0, 0, 0));
entity.setEnableInappLink("0110");// 若当前tbl_biz_template中没有数据,但是自增值到了6,那么执行这行代码后,返回的result_1为1,id为7
int result_1 = bizTemplateMapper.store2(entity);System.out.println("第一次插入的结果: " + result_1);
System.out.println("id: " + entity.getId());
此时若插入同tmpl_biz_type,tmpl_no,effective_date的数据,将会抛出重复key异常,There was an unexpected error (type=Internal Server Error, status=500). ### Error updating database. Cause: java.sql.SQLIntegrityConstraintViolationException: Duplicate entry 'type01-no01-2025-11-30 00:00:00' for key 'uk'。此时就可以使用upsert语法了
upsert语法初体验
基础情况1:唯一键冲突,列值未作实际修改,返回的结果是1(也可以是0)
1、truncate tbl_biz_template表后,插入数据,现在数据库中已经存在1条这样的数据了,注意下面的联合唯一键
insert into tbl_biz_template (id, tmpl_biz_type, tmpl_no, effective_date, tmpl_version, audit_time, enable_inapp_link) values (null, 'type01', 'no01', '2025-11-30', 'version_01', '2025-10-25', '0110');

2、mapper接口
public interface BizTemplateMapper extends BaseMapper<BizTemplateEntity> {int upsert1(@Param("entity") BizTemplateEntity entity);}
3、mapper.xml
<insert id="upsert1" useGeneratedKeys="true" keyProperty="id">insert into tbl_biz_template(tmpl_biz_type,tmpl_no,effective_date,tmpl_version,audit_time,enable_inapp_link)values (#{entity.tmplBizType},#{entity.tmplNo},#{entity.effectiveDate},#{entity.tmplVersion},#{entity.auditTime},#{entity.enableInappLink})on duplicate key updatetmpl_version = #{entity.tmplVersion},audit_time = #{entity.auditTime},enable_inapp_link = #{entity.enableInappLink}
</insert>
4、执行下面代码(由于DB中已经存在了唯一键,因此代码会去更新,但没有改动实际的值(值和原来保持一样))后,返回的结果是1,id为null。虽然返回的是1,但是通过查询表,发现实际上并没有插入,同时发现自增值现在是3了,这说明尝试插入由于唯一键重复导致失败了,然后主键自增了,然后执行了更新(虽然这次更新啥值都没改)。
但是我们可以发现:
1、使用这种方法当遇到唯一键重复的情况,我们执行这种语句并不会报错;
2、当遇到唯一键重复时,即使没有列值被修改,返回的值是1(默认情况下如果不配置jdbcUrl的连接参数useAffectedRows或者useAffectedRows=false,那么返回的是1;如果配置了useAffectedRows=true,返回的就是0)
BizTemplateEntity entity = new BizTemplateEntity();
entity.setTmplBizType("type01");
entity.setTmplNo("no01");
entity.setEffectiveDate(LocalDateTime.of(2025, 11, 30, 0, 0, 0));
entity.setTmplVersion("version_01");
entity.setAuditTime(LocalDateTime.of(2025, 10, 25, 0, 0, 0));
entity.setEnableInappLink("0110");int result_1 = bizTemplateMapper.upsert1(entity);System.out.println("插入的结果: " + result_1);
System.out.println("id: " + entity.getId());
基础情况2:未有唯一键冲突,实际插入1条数据,返回的结果为1(只会是1)
将tbl_biz_template中的数据使用delete from tbl_biz_template删掉,执行alter table tbl_biz_template auto_increment = 4 将自增值设置为4,再次执行。现在返回插入的结果: 1,id为4
(无论是否配置useAffectedRows或者useAffectedRows=false 亦或useAffectedRows=true,都是返回的1)
BizTemplateEntity entity = new BizTemplateEntity();
entity.setTmplBizType("type01");
entity.setTmplNo("no01");
entity.setEffectiveDate(LocalDateTime.of(2025, 11, 30, 0, 0, 0));
entity.setTmplVersion("version_01");
entity.setAuditTime(LocalDateTime.of(2025, 10, 25, 0, 0, 0));
entity.setEnableInappLink("0110");int result_1 = bizTemplateMapper.upsert1(entity);System.out.println("插入的结果: " + result_1);
System.out.println("id: " + entity.getId());
基础情况3:唯一键冲突,任意列值作了实际修改,返回的结果是2(只会是2)
将tbl_biz_template中的数据使用delete from tbl_biz_template;删掉,
再插入数据:insert into tbl_biz_template (id, tmpl_biz_type, tmpl_no, effective_date, tmpl_version, audit_time, enable_inapp_link) values (null, 'type01', 'no01', '2025-11-30', 'version_01', '2025-10-25', '0110');
执行 alter table tbl_biz_template auto_increment = 4 将自增值设置为4,再次执行。现在返回插入的结果: 2,id为4
(无论是否配置useAffectedRows或者useAffectedRows=false 亦或useAffectedRows=true,都是返回的2)
BizTemplateEntity entity = new BizTemplateEntity();
entity.setTmplBizType("type01");
entity.setTmplNo("no01");
entity.setEffectiveDate(LocalDateTime.of(2025, 11, 30, 0, 0, 0));
entity.setTmplVersion("version_01");
entity.setAuditTime(LocalDateTime.of(2025, 10, 25, 0, 0, 0));
entity.setEnableInappLink("1100");int result_1 = bizTemplateMapper.upsert1(entity);System.out.println("插入的结果: " + result_1);
System.out.println("id: " + entity.getId());
插入或按条件更新
1、重置表
-- 重置表,插入初始语句
drop table if exists tbl_biz_template;
create table tbl_biz_template (id int(10) primary key auto_increment,tmpl_biz_type varchar(10),tmpl_no varchar(10),effective_date datetime,tmpl_version varchar(10),audit_time datetime,enable_inapp_link varchar(10),-- 唯一键unique key uk(tmpl_biz_type, tmpl_no, effective_date)
);
select * from tbl_biz_template;
2、定义xml
<insert id="upsert2" useGeneratedKeys="true" keyProperty="id">insert into tbl_biz_template(tmpl_biz_type,tmpl_no,effective_date,tmpl_version,audit_time,enable_inapp_link)values (#{entity.tmplBizType},#{entity.tmplNo},#{entity.effectiveDate},#{entity.tmplVersion},#{entity.auditTime},#{entity.enableInappLink})on duplicate key updatetmpl_version = if(#{entity.auditTime, typeHandler=com.zzhua.common.handler.AuditTimeHandler} > audit_time, values(tmpl_version), tmpl_version),audit_time = if(#{entity.auditTime, typeHandler=com.zzhua.common.handler.AuditTimeHandler} > audit_time, values(audit_time), audit_time),-- 此处也可以不用values(列名)去引用原来要插入到该列的值enable_inapp_link = if(#{entity.auditTime, typeHandler=com.zzhua.common.handler.AuditTimeHandler} > audit_time, #{entity.enableInappLink}, enable_inapp_link)
</insert>
public class AuditTimeHandler extends BaseTypeHandler<LocalDateTime> {private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");@Overridepublic void setNonNullParameter(PreparedStatement ps, int i, LocalDateTime p, JdbcType jdbcType) throws SQLException {ps.setString(i, DATE_TIME_FORMATTER.format(p));}@Overridepublic LocalDateTime getNullableResult(ResultSet rs, String columnName) throws SQLException {return null;}@Overridepublic LocalDateTime getNullableResult(ResultSet rs, int columnIndex) throws SQLException {return null;}@Overridepublic LocalDateTime getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {return null;}
}
3、执行一遍,返回结果1,id为1(符合基础情况2)
BizTemplateEntity entity = new BizTemplateEntity();
entity.setTmplBizType("type01");
entity.setTmplNo("no01");
entity.setEffectiveDate(LocalDateTime.of(2025, 11, 30, 0, 0, 0));
entity.setTmplVersion("version_01");
entity.setAuditTime(LocalDateTime.of(2025, 10, 25, 0, 0, 0));
entity.setEnableInappLink("0110");int result_1 = bizTemplateMapper.upsert2(entity);System.out.println("插入的结果: " + result_1);
System.out.println("id: " + entity.getId());
4、上面的参数都不变,再执行一遍,返回结果1,id为null。(符合上面的基础情况1,未配置useAffectedRows)
5、修改auditTime,如下方由10改为11,执行后,返回结果2,id为null(符合基础情况3)
BizTemplateEntity entity = new BizTemplateEntity();
entity.setTmplBizType("type01");
entity.setTmplNo("no01");
entity.setEffectiveDate(LocalDateTime.of(2025, 11, 30, 0, 0, 0));
entity.setTmplVersion("version_01");
entity.setAuditTime(LocalDateTime.of(2025, 11, 25, 0, 0, 0));
entity.setEnableInappLink("1110");int result_1 = bizTemplateMapper.upsert2(entity);System.out.println("插入的结果: " + result_1);
System.out.println("id: " + entity.getId());
多行插入或更新 (批量)
1、重置表
drop table if exists tbl_biz_template;
create table tbl_biz_template (id int(10) primary key auto_increment,tmpl_biz_type varchar(10),tmpl_no varchar(10),effective_date datetime,tmpl_version varchar(10),audit_time datetime,enable_inapp_link varchar(10),-- 唯一键unique key uk(tmpl_biz_type, tmpl_no, effective_date)
);
select * from tbl_biz_template;
2、xml
<insert id="upsert3" useGeneratedKeys="true" keyProperty="id">insert into tbl_biz_template(tmpl_biz_type,tmpl_no,effective_date,tmpl_version,enable_inapp_link)values<foreach collection="list" item="entity" separator=",">(#{entity.tmplBizType},#{entity.tmplNo},#{entity.effectiveDate},#{entity.tmplVersion} ,#{entity.enableInappLink})</foreach>on duplicate key updatetmpl_version = values(tmpl_version),enable_inapp_link = values(enable_inapp_link)
</insert>
3、java
List<BizTemplateEntity> list = new ArrayList<>();BizTemplateEntity entity1 = new BizTemplateEntity();
entity1.setTmplBizType("type01");
entity1.setTmplNo("no01");
entity1.setEffectiveDate(LocalDateTime.of(2025, 11, 30, 0, 0, 0));
entity1.setTmplVersion("version_01");
entity1.setEnableInappLink("0110");BizTemplateEntity entity2 = new BizTemplateEntity();
entity2.setTmplBizType("type01");
entity2.setTmplNo("no02");
entity2.setEffectiveDate(LocalDateTime.of(2025, 11, 30, 0, 0, 0));
entity2.setTmplVersion("version_01");
entity2.setEnableInappLink("0110");// entity3 与 entity2 相比,只改了 enableInappLink
BizTemplateEntity entity3 = new BizTemplateEntity();
entity3.setTmplBizType("type01");
entity3.setTmplNo("no02");
entity3.setEffectiveDate(LocalDateTime.of(2025, 11, 30, 0, 0, 0));
entity3.setTmplVersion("version_01");
entity3.setEnableInappLink("1110");// entity4 与 entity3 相比,只改了 enableInappLink
BizTemplateEntity entity4 = new BizTemplateEntity();
entity4.setTmplBizType("type01");
entity4.setTmplNo("no02");
entity4.setEffectiveDate(LocalDateTime.of(2025, 11, 30, 0, 0, 0));
entity4.setTmplVersion("version_01");
entity4.setEnableInappLink("1111");// entity5 与 entity4 相同
BizTemplateEntity entity5 = new BizTemplateEntity();
entity5.setTmplBizType("type01");
entity5.setTmplNo("no02");
entity5.setEffectiveDate(LocalDateTime.of(2025, 11, 30, 0, 0, 0));
entity5.setTmplVersion("version_01");
entity5.setEnableInappLink("1111");// entity6 与 entity5 相同
BizTemplateEntity entity6 = new BizTemplateEntity();
entity6.setTmplBizType("type01");
entity6.setTmplNo("no02");
entity6.setEffectiveDate(LocalDateTime.of(2025, 11, 30, 0, 0, 0));
entity6.setTmplVersion("version_01");
entity6.setEnableInappLink("1111");list.add(entity1);
list.add(entity2);
list.add(entity3);
list.add(entity4);
list.add(entity5);
list.add(entity6);int result = bizTemplateMapper.upsert3(list);
System.out.println("批量插入的结果: " + result);
System.out.println("批量插入的ID: " + entity1.getId());
System.out.println("批量插入的ID: " + entity2.getId());
System.out.println("批量插入的ID: " + entity3.getId());
System.out.println("批量插入的ID: " + entity4.getId());
System.out.println("批量插入的ID: " + entity5.getId());
System.out.println("批量插入的ID: " + entity6.getId());
4、执行结果
批量插入的结果: 8
批量插入的ID: 1
批量插入的ID: null
批量插入的ID: null
批量插入的ID: null
批量插入的ID: null
批量插入的ID: null
5、查看数据

MySQL中ON DUPLICATE KEY UPDATE的介绍与使用、批量更新、存在即更新不存在则插入
有时候由于业务需求,可能需要先去根据某一字段值查询数据库中是否有记录,有则更新,没有则插入。这个时候就可以用到ON DUPLICATE KEY UPDATE这个sql语句了。
以下内容基于本地windows环境mysql:8.0.34进行讲解。
一、ON DUPLICATE KEY UPDATE的介绍
基本用法:ON DUPLICATE KEY UPDATE是一种MySQL的语法,它在插入新数据时,如果遇到唯一键冲突(即已存在相同的唯一键值),则会执行更新操作,而不是抛出异常或忽略该条数据。这个语法可以大大简化我们的代码,减少不必要的判断和查询操作。
用法总结
1:on duplicate key update 语句根据主键id或唯一键来判断当前插入是否已存在。
2:记录已存在时,只会更新on duplicate key update之后指定的字段。
3:如果同时传递了主键和唯一键,以主键为判断存在依据,唯一键字段内容可以被修改。
4:唯一键大小写敏感时,大小写不同的值被认为是两个值,执行插入。参见下文中的大小写敏感问题
二、ON DUPLICATE KEY UPDATE的使用
准备表结构及测试数据, 注意:name是唯一键
drop table if exists tbl_test;
create table tbl_test(id int primary key auto_increment,name varchar(30) unique not null,age int comment '年龄',address varchar(50) comment '住址',update_time datetime default null
) comment '测试表';insert into tbl_test(name,age,address,update_time) values('zhangsan',20,'杭州',now()),('lisi',21,'武汉',now());
测试数据如下:

2.1、案例一:根据主键id进行更新
on dupdate key update 语句基本功能是:当表中没有原来记录时,就插入,有的话就更新。
如下sql:
insert into tbl_test(id,name,age,address,update_time) values(1,'zhangsan1',201,'杭州1','2024-03-05 15:59:35')
on duplicate key update
age = values(age), -- 注意:values()括号中的内容是字段名称。比如:在java程序中使用时表字段可能叫user_name, 实体类中是userName,values()里面要填user_name
address = values(address),
update_time=now();

从执行结果可以看出,更新了id为1的age,address两个字段,而name字段没有修改生效。由此我们可以得出两个重要结论:
1:on duplicate key update 语句根据主键id来判断当前插入是否已存在。
2:已存在时,只会更新on duplicate key update之后限定的字段。
2.2、案例二:根据唯一索引进行更新(常用)
根据唯一索引进行更新是生产中比较常用的方式,因为id一般使用的是自增,很少会先把id查询出来,然后根据id进行更新。
如下sql:
insert into tbl_test(name,age,address) values('zhangsan',202,'杭州2')
on duplicate key update
age = values(age), -- 注意:values()括号中的内容是字段名称。比如:在java程序中使用时表字段可能叫user_name, 实体类中是userName,values()里面要填user_name
address = values(address),
update_time=now();

从执行结果看,这次没有传id,但是age,address字段仍然更新了。
由此可以得出另一个结论:
3:on duplicate key update 语句也可以根据唯一键来判断当前插入的记录是否已存在。
2.3、案例三:没有主键或唯一键字段值相同就插入
如下sql:
insert into tbl_test(name,age,address) values('zhangsan3',203,'杭州3')
on duplicate key update
age = values(age), -- 注意:values()括号中的内容是字段名称。比如:在java程序中使用时表字段可能叫user_name, 实体类中是userName,values()里面要填user_name
address = values(address),
update_time=now();

这条执行就比较简单了,没有主键或唯一键字段值相同,即判断当前记录不存在,新插入一条。
注意: 这里我们发现主键id并没有连续,直接从2变成了4,具体原理可见《MySQL数据库设置主键自增、自增主键为什么不能保证连续递增》
2.4、案例四:主键与唯一键字段同时存在
如下sql:
insert into tbl_test(id,name,age,address) values(1,'zhangsan4',204,'杭州4')
on duplicate key update
name = values(name),
age = values(age), -- 注意:values()括号中的内容是字段名称。比如:在java程序中使用时表字段可能叫user_name, 实体类中是userName,values()里面要填user_name
address = values(address),
update_time=now();

从上面可以看出,连唯一键name也被修改了。结论:
4:如果传递了主键,是可以修改唯一键字段内容的。
这里要注意,如果这里的name修改为 lisi,zhangsan3
会报唯一键冲突的。可以自行尝试。
三、ON DUPLICATE KEY UPDATE的注意事项
3.1、on dupdate key update之后values的使用事项
如下sql:
insert into tbl_test(name,age,address,update_time) values('zhangsan4',205,'杭州5','2024-03-05 00:00:00')
on duplicate key update
age = age,
address = '杭州',
update_time=values(update_time);

on dupdate key update之后没有用values的情况
分为两种情况:
1:如果为如上面的address= “杭州”,则会一直更新为"杭州".
2:如果为如上面的age = age,则age会保持数据库中的值,不会更新。
3:只有当使用了values后,才会更新为上下文中传入的值
3.2、对values使用判断
如下sql
insert into tbl_test(id,name,age,address) values(1,'zhangsan',202,'杭州2')
on duplicate key update
name = ifnull(values(name),name),
age = values(age)
达到的效果是,如果传入的name值为null,则不更新。不为null则更新。这里与mybatis配合使用比较好。
3.3、唯一索引大小写敏感问题
思考这么一个问题:如上面name作为唯一索引,当name大小写敏感时且数据库中存储了name=“zhangsan” ,那么再插入name="ZHANGSAN"是更新还是新增?
1):唯一索引大小写不敏感时
设置name字段为唯一索引且大小写不敏感
drop table if exists tbl_test;
create table tbl_test(id int primary key auto_increment,name varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci unique not null,age int comment '年龄',address varchar(50) comment '住址',update_time datetime default null
) comment '测试表';insert into tbl_test(name,age,address,update_time) values('zhangsan',20,'杭州',now());
insert into tbl_test(name,age,address,update_time) values('ZHANGSAN',21,'杭州1',now());

可以看到当字段为大小写不敏感时zhangsan跟ZHANGSAN被认为是同一个值,不能重复插入。
当数据库中name=zhangsan时且name字段大小写不敏感时,我们看一下name="ZHANGSAN"能否更新成功?
insert into tbl_test(name,age,address,update_time) values('ZHANGSAN',22,'杭州2','2024-03-05 00:00:00')
on duplicate key update
age = values(age),
address = values(address),
update_time=values(update_time);

以上结果可以看出,当大小写不敏感时on duplicate key update是可以更新成功的,即认为是同一个值。
2):唯一索引大小写敏感时
设置name字段为唯一索引且大小写敏感
drop table if exists tbl_test;
create table tbl_test(id int primary key auto_increment,name varchar(30) CHARACTER SET utf8 COLLATE utf8_bin unique not null,age int comment '年龄',address varchar(50) comment '住址',update_time datetime default null
) comment '测试表';insert into tbl_test(name,age,address,update_time) values('zhangsan',20,'杭州',now());
insert into tbl_test(name,age,address,update_time) values('ZHANGSAN',21,'杭州1',now());

可以看到当字段为大小写敏感时zhangsan跟ZHANGSAN被认为是两个值,插入了两条记录。所以此时用on duplicate key update会执行新增操作
四、ON DUPLICATE KEY UPDATE与mybatis联合使用
4.1、写法一:与values()联合使用
注意:values后面的内容是表字段名称即带下划线,而不是实体类驼峰名称
下面的写法支持单条更新与批量更新
如下sql: dept_id为主键或唯一索引
<insert id="replaceInto">INSERT INTO sys_dept(dept_id,parent_id,status,update_time) VALUES<foreach collection="deptList" item="item" separator=",">(#{item.deptId},#{item.parentId},#{item.status},#{item.updateTime})</foreach>ON DUPLICATE KEY UPDATEparent_id=VALUES(parent_id),status=VALUES(status),update_time=VALUES(update_time)
</insert>
4.2、写法二:使用#{}
如下sql: dept_id为主键或唯一索引
注意: #{}方式仅支持单条插入更新,不支持批量插入或更新
<insert id="replaceInto">INSERT INTO sys_dept(dept_id,parent_id,status,update_time) VALUES(#{deptId},#{parentId},#{status},#{updateTime})</foreach>ON DUPLICATE KEY UPDATEparent_id = #{parentId},status = #{status},update_time = #{updateTime}
</insert>
五、ON DUPLICATE KEY UPDATE的缺点及坑
5.1、ON DUPLICATE KEY UPDATE每次更新导致id不连续
如下sql:
drop table if exists tbl_test;
create table tbl_test(id int primary key auto_increment,name varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci unique not null,age int comment '年龄',address varchar(50) comment '住址',update_time datetime default null
) comment '测试表';insert into tbl_test(name,age,address,update_time) values('zhangsan',20,'杭州',now()),('李四',21,'武汉',now());

执行on duplicate key update进行更新,然后再插入一条新的数据
insert into tbl_test(name,age,address,update_time) values('zhangsan',22,'杭州2','2024-03-05 00:00:00')
on duplicate key update
age = values(age),
address = values(address),
update_time=values(update_time);insert into tbl_test(name,age,address,update_time) values('王五',23,'深圳',now());

可以看到id自增值从2直接变成了4,造成了id的不连续。
1.ON DUPLICATE KEY UPDATE每次更新导致id不连续原理:
mysql中有个配置值是innodb_autoinc_lock_mode。
innodb_autoinc_lock_mode中有3中模式,0,1和2,mysql5的默认配置是1,
- 0是每次分配自增id的时候都会锁表.
- 1只有在bulk insert的时候才会锁表,简单insert的时候只会使用一个light-weight mutex,比0的并发性能高
- 2.没有仔细看,好像是很多的不保证…不太安全.
数据库默认是1的情况下,就会发生上面的那种现象,每次使用insert into … on duplicate key update 的时候都会把简单自增id增加,不管是发生了insert还是update
5.2、death lock死锁
经常看到网上说ON DUPLICATE KEY UPDATE会导致死锁,确实是存在这个可能的,不过由于目前没有特别好的方案,所以也只能使用这个sql语法了。在执行insert ... on duplicate key语句时,如果不对同一个表同时进行并发的insert或者update,基本不会造成死锁。即insert ... on duplicate key时尽量单线程串行进行新增或更新
insert … on duplicate key 在执行时,innodb引擎会先判断插入的行是否产生重复key错误,如果存在,在对该现有的行加上S(共享锁)锁,如果返回该行数据给mysql,然后mysql执行完duplicate后的update操作,然后对该记录加上X(排他锁),最后进行update写入。
如果有两个事务并发的执行同样的语句,那么就会产生death lock,如:

