关注“数据和云”,精彩不容错过

前言

开发与维护人员避免不了与 in/exists、not in/not exists 子查询打交道,接触过的人可能知道 in/exists、not in/not exists 相关子查询会使 SELECT 查询变慢,没有 join 连接效率,却不知道 DELETE、UPDATE 下的子查询却可能导致更严重的锁问题,直接导致 MySQL InnoDB 行锁机制失效,锁升级,严重影响数据库的并发和性能。对大表或高并发的表的执行 DELETE、UPDATE 子查询操作,甚至可能导致业务长时间不可用。

MySQL 下的 InnoDB 行锁,是通过以位图方式对 index page 加锁机制来实现的。而不是直接对相应的数据行和相关的 data page 加锁,这样的加锁实现就导致了其行锁实现的不稳定性。InnoDB这种行锁实现特点意味着:只有通过有效索引条件检索数据行,InnoDB 才使用行级锁,否则,InnoDB 将使用表锁!UPDATE、DELETE 子查询条件下优化器的实现导致子查询下的行锁机制失效,行锁升级,对更多无关的行数据加锁,进而影响数据库并发和性能 。

一、UPDATE、DELETE 子查询锁机制失效解析及优化方案

下面以普通的 UPDATE 关联子查询更新来详解子查询对锁机制的影响及具体优化解决方案:

子查询下的事务、锁机制分析: 
优化器实现:

UPDATE pay_stream a   SET a.return_amount =(SELECT b.cash_amount    FROM pay_main b     WHERE a.pay_id = b.pay_id    AND b.user_name = '1388888888');

id  select_type         table   partitions  type    possible_keys          key      key_len  ref                         rows  filtered  Extra        ------  ------------------  ------  ----------  ------  ---------------------  -------  -------  ------------------------  ------  --------  -------------1  UPDATE              a       (NULL)      index   (NULL)                 PRIMARY  98       (NULL)                    155041    100.00  (NULL)       2  DEPENDENT SUBQUERY  b       (NULL)      eq_ref  PRIMARY,idx_user_name  PRIMARY  98       settlement_data.a.pay_id       1      5.00  Using where 

从执行计划可以看出该 update 子查询,优化器先执行了 id 为2的 (DEPENDENT SUBQUERY )相关子查询部分,然后通过对 PRIMARY 以索引全扫描方式对全表 155041 行数据加锁主锁,来执行的 update 操作,阻碍了了表的update、delete并发操作。 

事物、锁验证: 

事物一:

事物二: 

事务二果真被事务一阻塞,事务一的子查询操作的确锁住了不相关的数据行,阻碍了数据库的并发操作。


关联更新下的事物、锁机制分析: 

优化器实现:

UPDATE pay_stream a INNER JOIN pay_main b ON a.pay_id = b.pay_id   SET a.return_amount = b.cash_amount WHERE b.user_name = '1388888888';

id  select_type  table   partitions  type    possible_keys          key            key_len  ref                         rows  filtered  Extra   ------  -----------  ------  ----------  ------  ---------------------  -------------  -------  ------------------------  ------  --------  --------     1  SIMPLE       b       (NULL)      ref     PRIMARY,idx_user_name  idx_user_name  387      const                          1    100.00  (NULL)       1  UPDATE       a       (NULL)      eq_ref  PRIMARY                PRIMARY        98       settlement_data.b.pay_id       1    100.00  (NULL)

从执行计划可以看出,优化器先执行了通过 idx_user_name 索引执行了 b 表的检索操作,然后再通过 eq_ref  方式关联 PRIMARY 更新了一行数据,并没引起行锁升级,影响表的并发操作。事务机制验证如下:

事物一:

事物二:

不难看出 普通 join 关联更新只对需要更新的数据行加索,更有利于数据库的并发操作。

二、其它场景下UPDATE 、DELETE子查询的优化方案


in/exists 子查询 
in 子查询下优化器实现:

UPDATE pay_stream a   SET a.return_amount = 0 WHERE a.pay_id IN (SELECT b.pay_id  FROM pay_main b WHERE  b.user_name = '1388888888');    id  select_type         table   partitions  type             possible_keys          key      key_len  ref       rows  filtered  Extra        ------  ------------------  ------  ----------  ---------------  ---------------------  -------  -------  ------  ------  --------  -------------     1  UPDATE              a       (NULL)      index            (NULL)                 PRIMARY  98       (NULL)  155044    100.00  Using where       2  DEPENDENT SUBQUERY  b       (NULL)      unique_subquery  PRIMARY,idx_user_name  PRIMARY  98       func         1      5.00  Using where  DELETE a FROM pay_stream a WHERE a.pay_id IN (SELECT b.pay_id  FROM pay_main b WHERE  b.user_name = '1388888888');    id  select_type  table   partitions  type    possible_keys          key            key_len  ref                         rows  filtered  Extra        ------  -----------  ------  ----------  ------  ---------------------  -------------  -------  ------------------------  ------  --------  -------------     1  SIMPLE       b       (NULL)      ref     PRIMARY,idx_user_name  idx_user_name  387      const                          1    100.00  Using index       1  DELETE       a       (NULL)      eq_ref  PRIMARY                PRIMARY        98       settlement_data.b.pay_id       1    100.00  (NULL)


exists 子查询下优化器实现:

UPDATE pay_stream a   SET a.return_amount = 0 WHERE EXISTS (SELECT b.pay_id  FROM pay_main b WHERE a.pay_id = b.pay_id AND b.user_name = '1388888888');    id  select_type         table   partitions  type    possible_keys          key      key_len  ref                         rows  filtered  Extra        ------  ------------------  ------  ----------  ------  ---------------------  -------  -------  ------------------------  ------  --------  -------------     1  UPDATE              a       (NULL)      index   (NULL)                 PRIMARY  98       (NULL)                    155044    100.00  Using where       2  DEPENDENT SUBQUERY  b       (NULL)      eq_ref  PRIMARY,idx_user_name  PRIMARY  98       settlement_data.a.pay_id       1      5.00  Using whereDELETE a FROM pay_stream a WHERE EXISTS (SELECT 1  FROM pay_main b WHERE  a.pay_id = b.pay_id AND b.user_name = '1388888888');    id  select_type         table   partitions  type    possible_keys          key      key_len  ref                         rows  filtered  Extra        ------  ------------------  ------  ----------  ------  ---------------------  -------  -------  ------------------------  ------  --------  -------------     1  DELETE              a       (NULL)      ALL     (NULL)                 (NULL)   (NULL)   (NULL)                    155044    100.00  Using where       2  DEPENDENT SUBQUERY  b       (NULL)      eq_ref  PRIMARY,idx_user_name  PRIMARY  98       settlement_data.a.pay_id       1      5.00  Using where


inner join 下优化器实现:

UPDATE pay_stream a INNER JOIN pay_main b ON a.pay_id = b.pay_id   SET a.return_amount = 0 WHERE b.user_name = '1388888888';    id  select_type  table   partitions  type    possible_keys          key            key_len  ref                         rows  filtered  Extra        ------  -----------  ------  ----------  ------  ---------------------  -------------  -------  ------------------------  ------  --------  -------------     1  SIMPLE       b       (NULL)      ref     PRIMARY,idx_user_name  idx_user_name  387      const                          1    100.00  Using index       1  UPDATE       a       (NULL)      eq_ref  PRIMARY                PRIMARY        98       settlement_data.b.pay_id       1    100.00  (NULL)    DELETE a FROM pay_stream a INNER JOIN pay_main b ON a.pay_id = b.pay_id  WHERE b.user_name = '1388888888';    id  select_type  table   partitions  type    possible_keys          key            key_len  ref                         rows  filtered  Extra        ------  -----------  ------  ----------  ------  ---------------------  -------------  -------  ------------------------  ------  --------  -------------     1  SIMPLE       b       (NULL)      ref     PRIMARY,idx_user_name  idx_user_name  387      const                          1    100.00  Using index       1  DELETE       a       (NULL)      eq_ref  PRIMARY                PRIMARY        98       settlement_data.b.pay_id       1    100.00  (NULL)

从上述的优化器行为不难看出,inner join 联表的情况下,只对需更新的数据行加索,并发性能最高;exitsts 子查询在 delete 与 update 操作下,均为全索引扫描,并发最差;in 子查询在 update 操作下与 exists 一样为全索引扫描,而在 delete 操作下为主键操作,只对对应的行更新的数据行加索,并发次之。

not in /not exists 子查询 

not in 子查询下优化器实现:

UPDATE pay_stream a   SET a.return_amount = 0  WHERE a.pay_id NOT IN (SELECT b.pay_id     FROM pay_main b WHERE  b.pay_time > '2017-08-12 00:00:00');    id  select_type         table   partitions  type             possible_keys                  key      key_len  ref       rows  filtered  Extra        ------  ------------------  ------  ----------  ---------------  -----------------------------  -------  -------  ------  ------  --------  -------------     1  UPDATE              a       (NULL)      index            (NULL)                         PRIMARY  98       (NULL)  155182    100.00  Using where       2  DEPENDENT SUBQUERY  b       (NULL)      unique_subquery  PRIMARY,IDX_PAY_MAIN_PAY_TIME  PRIMARY  98       func         1     46.46  Using where  DELETE a FROM pay_stream a WHERE a.pay_id NOT IN (SELECT b.pay_id  FROM pay_main b WHERE b.pay_time >= '2017-08-12 00:00:00');    id  select_type         table   partitions  type             possible_keys                  key      key_len  ref       rows  filtered  Extra        ------  ------------------  ------  ----------  ---------------  -----------------------------  -------  -------  ------  ------  --------  -------------     1  DELETE              a       (NULL)      ALL              (NULL)                         (NULL)   (NULL)   (NULL)  155182    100.00  Using where       2  DEPENDENT SUBQUERY  b       (NULL)      unique_subquery  PRIMARY,IDX_PAY_MAIN_PAY_TIME  PRIMARY  98       func         1     46.46  Using where


not exists 子查询下优化器实现:

UPDATE pay_stream a   SET a.return_amount = 0 WHERE NOT EXISTS (SELECT b.pay_id    FROM pay_main b WHERE a.pay_id = b.pay_id AND b.pay_time > '2017-08-12 00:00:00');    id  select_type         table   partitions  type    possible_keys                  key      key_len  ref                rows  filtered  Extra        ------  ------------------  ------  ----------  ------  -----------------------------  -------  -------  ---------------  ------  --------  -------------     1  UPDATE              a       (NULL)      index   (NULL)                         PRIMARY  98       (NULL)           155182    100.00  Using where       2  DEPENDENT SUBQUERY  b       (NULL)      eq_ref  PRIMARY,IDX_PAY_MAIN_PAY_TIME  PRIMARY  98       settle.a.pay_id       1     46.46  Using whereDELETE a FROM pay_stream a WHERE NOT EXISTS (SELECT 1          FROM pay_main b         WHERE a.pay_id = b.pay_id AND b.pay_time >= '2017-08-12 00:00:00');    id  select_type         table   partitions  type    possible_keys                  key      key_len  ref                rows  filtered  Extra        ------  ------------------  ------  ----------  ------  -----------------------------  -------  -------  ---------------  ------  --------  -------------     1  DELETE              a       (NULL)      ALL     (NULL)                         (NULL)   (NULL)   (NULL)           155182    100.00  Using where       2  DEPENDENT SUBQUERY  b       (NULL)      eq_ref  PRIMARY,IDX_PAY_MAIN_PAY_TIME  PRIMARY  98      settle.a.pay_id       1     46.46  Using where


left join 下优化器实现:

UPDATE pay_stream a LEFT JOIN pay_main b  ON a.pay_id = b.pay_id AND b.pay_time >= '2017-08-12 00:00:00'   SET a.return_amount = 0 WHERE b.pay_id IS NULL;    id  select_type  table   partitions  type    possible_keys                  key      key_len  ref                rows  filtered  Extra                    ------  -----------  ------  ----------  ------  -----------------------------  -------  -------  ---------------  ------  --------  -------------------------     1  UPDATE       a       (NULL)      ALL     (NULL)                         (NULL)   (NULL)   (NULL)           155182    100.00  (NULL)                       1  SIMPLE       b       (NULL)      eq_ref  PRIMARY,IDX_PAY_MAIN_PAY_TIME  PRIMARY  98       settle.a.pay_id       1    100.00  Using where; Not exists  DELETE a FROM pay_stream a LEFT JOIN pay_main b  ON a.pay_id = b.pay_id AND b.pay_time >= '2017-08-12 00:00:00' WHERE b.pay_id IS NULL;    id  select_type  table   partitions  type    possible_keys                  key      key_len  ref                rows  filtered  Extra                    ------  -----------  ------  ----------  ------  -----------------------------  -------  -------  ---------------  ------  --------  -------------------------     1  DELETE       a       (NULL)      ALL     (NULL)                         (NULL)   (NULL)   (NULL)           155182    100.00  (NULL)                       1  SIMPLE       b       (NULL)      eq_ref  PRIMARY,IDX_PAY_MAIN_PAY_TIME  PRIMARY  98       settle.a.pay_id       1    100.00  Using where; Not exists

从上述优化器的行为分析不难看出,left join 完全持有 a 表表锁,其间表完全失去了并发写入、更新操作;not in 与 not exists 执行计划类似,delete 操作下持有表锁,完全不支持并发,update 操作下以 PRIMARY 索引全扫描的方式,锁住了表中数据行,阻碍了对表的 delete,update 操作,却不妨碍 insert 的并发操作,MySQL 5.6 之后的优化器对 not in 子查询做了相关优化工作,检索效率高于 not exists。综上所述:delete、update下的 not in 子查询性能和并发度最高。

MySQL 优化器以及 InnoDB 行锁机制特性,增加了 UPDATE、DELETE 下子查询复杂的度,在 MySQL 数据库程序开发数据库维护过程中,真正了解优化器的实现和 InnoDB 行锁机制的行为,才有能设计出正真的高并发系统和更好的运维数据库。

作者:蔡亮。

投稿:有投稿意向技术人请在公众号对话框留言。

转载:意向文章下方留言。

点击 阅读原文 获得更多精彩。

更多精彩请关注 “数据和云” 公众号


资源下载

关注公众号:数据和云(OraNews)回复关键字获取

2018DTCC , 数据库大会PPT

2017DTC,2017 DTC 大会 PPT

DBALIFE ,“DBA 的一天”海报

DBA04 ,DBA 手记4 电子书

122ARCH ,Oracle 12.2体系结构图

2017OOW ,Oracle OpenWorld 资料

PRELECTION ,大讲堂讲师课程资料

近期文章

仅仅使用AWR做报告? 性能优化还未入门

实战课堂:一则CPU 100%的故障分析

杨廷琨:如何编写高效SQL(含PPT)

一份高达555页的技术PPT会是什么样子?

大象起舞:用PostgreSQL解海盗分金问题

ProxySQL!像C罗一样的强大

高手过招:用SQL解决环环相扣刑侦推理问题

SQL优化之一则MySQL中的DELETE、UPDATE 子查询的锁机制失效案例相关推荐

  1. mysql数据库优化课程---15、mysql优化步骤(mysql中最常用最立竿见影的优化是什么)...

    mysql数据库优化课程---15.mysql优化步骤(mysql中最常用最立竿见影的优化是什么) 一.总结 一句话总结:索引优化最立竿见影 索引优化:不然有多少行要扫描多少次,1亿行大概是5到10分 ...

  2. 【Mysql】慢SQL优化详解 Mysql案例

    前言 影响查询性能因素 –(定量为同一数据库 且并发 数据量一致)依次是: 机器配置 查询引擎 表设计 索引 SQL语句 什么是慢SQL 比通常执行慢.或者超过最大执行限定时间,通常是500ms 慢S ...

  3. mysql中的merge into,SQL Server 2008中利用merge into关键实现insert/update自动匹配(类似于MySQL中的For Update关键字)...

    SQL Server 2008中利用merge into关键实现insert/update自动匹配(类似于MySQL中的For Update关键字) 语法请参考: 按照语法编写语句 DECLARE @ ...

  4. SQL优化之组合索引中字段的顺序

    SQL优化之组合索引中字段的顺序 记一次SQL优化:组合索引中字段顺序有讲究,越离散的字段越靠前,哪个列可以降低索引扫描成本放在前面. Refer:https://blog.csdn.net/pan_ ...

  5. mysql中如何把两个查询结果列数不同并成一张表_MySQL

    引言 本文整理了MySQL相关的知识,方便以后查阅. 基础架构 下图是 MySQL 的一个简要架构图,从下图你可以很清晰的看到用户的 SQL 语句在 MySQL 内部是如何执行的. 先简单介绍一下下图 ...

  6. 【学习笔记】MySQL数据库高级版 - 索引优化、慢查询、锁机制等

    本文是尚硅谷周阳(阳哥)老师的MySQL高级篇视频的学习笔记.由于视频比较老,所以在高版本的MySQL中索引的地方做了优化,和视频的内容不完全一样,不过大体一致.从第四节锁机制开始的部分还没有整理. ...

  7. mysql in优化_MySQL的一次优化记录 (IN子查询和索引优化)

    这两天实习项目遇到一个网页加载巨慢的问题(10多秒),然后定位到是一个MySQL查询特别慢的语句引起的: SELECT * FROM ( SELECT DISTINCT t.vc_date, t.c_ ...

  8. 【转】MySQL中select * for update锁表的问题

    MySQL中select * for update锁表的问题 由于InnoDB预设是Row-Level Lock,所以只有「明确」的指定主键,MySQL才会执行Row lock (只锁住被选取的资料例 ...

  9. mysql中实现分类统计查询的步骤_在MySQL中如何进行分组统计查询

    昨天和大家分享了MySQL中,如何进行聚合函数及统计函数查询,若是不清楚的话,可以去看一下我的那个文章.今天继续和大家分享,在MySQL中如何进行分组统计查询,这个在实际应用中,也会经常运用到,比如以 ...

最新文章

  1. MongoDB之bson的介绍
  2. ProtonMail 开源其所有电子邮件应用程序
  3. linux下不同arm 编译器的异同
  4. Python nose单元测试框架的安装与使用
  5. Angularjs 开始之Hello world
  6. JavaSE:如何设置/获取您自己的文件和目录属性
  7. 暑假集训中期测试 Problem D: 装箱问题2 (并查集)
  8. SpringMVC中使用@RequestBody,@ResponseBody注解实现Java对象和XML/JSON数据自动转换)
  9. transition.tween
  10. LookupError: unknown encoding: cp65001及命令行无法输入中文问题(转)
  11. MATLAB 画图 x轴换成 字符串
  12. 移动端实现摇一摇并振动
  13. Maya2014/2015/2016/2017/2018/2019安装包及安装教程
  14. 阿里巴巴代码规范 学习总结
  15. 腔体缝隙天线[搬运]
  16. 微信支付商户号如何开通0.2%提现费率/手续费?
  17. 2022-01-15 OpenCV(3.4.1) Error: Image step is wrong (The matrix is not continuous, thus its
  18. Webpack的基本使用
  19. 根据出生日期获取农历信息
  20. 语音论文:用于端到端语音识别的简化完全量化的Transformer模型

热门文章

  1. 在线生成艺术字_生成艺术:如何修改绘画
  2. 武德 | 年轻人!这才叫真正的程序猿的武德
  3. Bootstrap3 模态对话框的尺寸
  4. CSS 字体调整 font-size-adjust属性
  5. 深度学习笔记(17) 误差分析(二)
  6. 在c语言中利用链表常见问题,C语言,链表中遇到棘手有关问题
  7. 正在等待缓存锁:无法获得锁_一句话说清分布式锁,进程锁,线程锁
  8. mysql灰度更新_灰度发布系统架构设计
  9. 机器学习--------SVM
  10. Docker(十七)-修改Docker容器启动配置参数