查看原文
其他

为什么不建议使用ON DUPLICATE KEY UPDATE?

关注Java就该这么学带你全面认识Java
Java就该这么学后台回复 1024 有特别礼包

责编:Java就该这么学 | 来源:网络

上一篇精彩:Spring Cloud 如何统一异常处理?写得太好了!
大家好,我是Java就该这么学。

昨天评审代码时,一群大佬看到有同事的代码里使用了mysql的on duplicate key update语法实现了对数据的save or update,说这个语法有严重的性能和其他隐患问题,必须改成先查询一次分出新增集合和修改集合,再分别进行批量新增和批量修改的方式进行,并对批量修改时使用case when的方式实现。

对于批量修改,在mybatis中也就是类似这种的xml:

<update id="updateByIds">
    update tb_user
    <trim prefix="set" suffixOverrides=",">
        <trim prefix="name = case" suffix="end,">
            <foreach collection="list" item="i" index="index">
                when id= #{i.id,jdbcType=VARCHAR} then #{i.name,jdbcType=VARCHAR}
            </foreach>
        </trim>
        <trim prefix="weight = case" suffix="end,">
            <foreach collection="list" item="i" index="index">
                when id= #{i.id,jdbcType=VARCHAR} then #{i.weight,jdbcType=DECIMAL}
            </foreach>
        </trim>
        <trim prefix="high = case" suffix="end,">
            <foreach collection="list" item="i" index="index">
                when id= #{i.id,jdbcType=VARCHAR} then #{i.high,jdbcType=DECIMAL}
            </foreach>
        </trim>
    </trim>
    where id in
    <foreach collection="list" item="item" open="(" close=")" separator=",">
        #{item.id,jdbcType=VARCHAR}
    </foreach>
</update>

公司同事写的批量修改 SQL。对于这种做法我也表示认同,但同事追问了一句,很想了解一下on duplicate key update到底有什么问题,很多同事也说不出具体的性能和隐患原因在哪里,所以我就写出了这篇文章。

官方资料

为了能更直接获取出最权威的信息,直接上mysql的官方说明查看有无对应的资料。根据使用的mysql版本查看对应的说明,如我这里的mysql5.7为例,其官方说明地址如下:

https://dev.mysql.com/doc/refman/5.7/en/insert-on-duplicate.html

其中对于「on duplicate key update」的使用方法也有非常详细的说明。


on duplicate key update

这里对于它的使用方法不做介绍,感兴趣的可以点开上面的链接进行详细的查看。

但为了对官方文档中的说明进行验证,这里根据官方的说明进行一个小实验进行验证。

创建一个t1表:

CREATE TABLE `t1` (
  `a` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增主键ID',
  `b` int(11),
  `c` int(11),
  PRIMARY KEY (`a`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COMMENT='临时测试表'

验证主键插入并更新功能

空表创建好后,多次执行如下sql。(此时只有自增主键a列)

INSERT INTO t1 (a,b,c) VALUES (1,2,3)
ON DUPLICATE KEY UPDATE c=c+1;

执行1次的结果:

abc
123

执行2次的结果:

abc
124

执行3次的结果:

abc
125

执行4次的结果:

abc
126

执行5次的结果:

abc
127

通过观察可知,上面的 sql 在主键已经存在时相当于如下 sql。

UPDATE t1 SET c=c+1 WHERE a=1;

再试下新增的 sql。

INSERT INTO t1 (b,c) VALUES (20,30)
ON DUPLICATE KEY UPDATE c=c+1;

abc
127
22030

新增记录成功,id 也自增正常。

验证多字段唯一索引问题

在官方资料中有这样的一句话:

If column b is also unique, the INSERT is equivalent to this UPDATE statement instead:

UPDATE t1 SET c=c+1 WHERE a=1 OR b=2 LIMIT 1;

If a=1 OR b=2 matches several rows, only one row is updated. In general, you should try to avoid using an ON DUPLICATE KEY UPDATE clause on tables with multiple unique indexes.

牛逼啊!接私活必备的 N 个开源项目!赶快收藏吧


接下来实验一下,给 t1 加的 b 也加上唯一索引:

ALTER TABLE t1 ADD UNIQUE INDEX uniq_b (b ASC);

然后执行如下 sql:

INSERT INTO t1 (a,b,c) VALUES (3,20,30)
ON DUPLICATE KEY UPDATE c=c+1;

其 t1 表结果如下:

abc
127
22031

从上面的结果可以看出,其只执行了 update 的操作,从而告诉了我们在使用 on duplicate key update 语句时,应当避免多个唯一索引的场景

当a是一个唯一索引(unique index)时,并且t1表中已经存在a为1的记录时,如下两个sql的效果是一样的。

INSERT INTO t1 (a,b,c) VALUES (1,2,3)
ON DUPLICATE KEY UPDATE c=c+1;

UPDATE t1 SET c=c+1 WHERE a=1;

ALTER TABLE t1 DROP INDEX uniq_b ;

ALTER TABLE ntocc_test.t1
ADD UNIQUE INDEX uniq_b (b ASC);
;

但在innoBD存储类型的表中,当a是一个自增主键时,其效果官方文档中的解释是这样的:

The effects are not quite identical: For an InnoDB table where a is an auto-increment column, the INSERT statement increases the auto-increment value but the UPDATE does not.

也就是如果只有一个主键,则会执行新增操作

但当b也是一个唯一索引时,就会执行更新操作
上面的语句就会变成这样的:

UPDATE t1 SET c=c+1 WHERE a=1 OR b=2 LIMIT 1;

If a=1 OR b=2 matches several rows, only one row is updated. In general, you should try to avoid using an ON DUPLICATE KEY UPDATE clause on tables with multiple unique indexes.

因此应当避免多唯一索引用on deplicate key update语法

涉及到的锁说明

同时,在查看官网资料中底部对于此语法的说明,从中看到如下描述:

An INSERT … ON DUPLICATE KEY UPDATE on a partitioned table using a storage engine such as MyISAM that employs table-level locks locks any partitions of the table in which a partitioning key column is updated. (This does not occur with tables using storage engines such as InnoDB that employ row-level locking.) For more information, see Section 22.6.4, “Partitioning and Locking”https://dev.mysql.com/doc/refman/5.7/en/partitioning-limitations-locking.html

主要是说在MyISAM的存储引擎中,on duplicate key update使用的是表级锁来进行实现的,那么就可以存在表级锁时的事务并发性能问题。另外,搜索公众号顶级科技后台回复“物联网平台”,获取一份惊喜礼包。

但是innoDB引擎中,on duplicate key update是用的行级锁进行实现的。

但同时查看了官方的bug列表,发现如下记录:https://bugs.mysql.com/bug.php?id=52020

官方的bug列表

其中有如下记录:

Hi,
I am facing this same issue in version 5.7.18. Deadlock error when multiple threads execute INSERT… ON DUPLICATE KEY UPDATE for bulk insert/update.
How it can be fixed?

I am facing the same issue when multiple threads are trying to insert in same table with primary key and unique index. Records are being inserted are different. Also It seems to be taking next-key lock here.

主要是说在并发事务的情况下,可能会导致死锁。

为了对此进行验证,我使用连接工具进行了验证,但可能是因为并发不够的原因,并没有产生死锁。

总结

  1. on duplicate key update在MyISAM存储引擎下使用的是表锁,性能不好

  2. on duplicate key update在InnoDB下并发事务情况下可能会存在锁表/死锁问题

  3. 应尽量避免在多唯一索引的情况下使用此语句


欢迎有需要的同学试试,如果本文对您有帮助,也请帮忙点个 赞 + 在看 啦!❤️

在 GitHub猿 还有更多优质项目系统学习资源,欢迎分享给其他同学吧!



公众号后台回复 Java 或者 面试 有惊喜礼包!Java就该这么学交流群

 「Java就该这么学」建立了读者Java交流群,大家可以添加小编微信进行加群。欢迎有想法、乐于分享的朋友们一起交流学习。

扫描添加好友邀你进Java群,加我时注明姓名+公司+职位】


版权申明:内容来源网络,版权归原作者所有。如有侵权烦请告知,我们会立即删除并表示歉意。谢谢。

往日文章:

想了解Java后端学习路线?看完这篇就够了!
Java这个高级特性,很多人还没用过!
Maven 最全教程,看了必懂,99% 的人都收藏了!
SpringBoot 启动时自动执行代码的几种方式,还有谁不会??
Typora 保姆级教程
几行代码,搞定 SpringBoot 接口恶意刷新和暴力请求!
看看人家那短链接系统,那叫一个优雅
还在 MySQL 中使用枚举?这些陷阱一定要注意!
一文帮你彻底掌握Nginx
这是我见过最好的物联网平台!
惊呆了!国产IDE发布,由阿里研发,完全开源!(高性能+高定制性)

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存