热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

UPDATE能走索引还会锁全表吗

导读执行UPDATE时,WEHRE条件列虽已有索引,但还会锁全表,肿么回事?问题描述叶师傅有次上课过程中执行UPDATE测试案例时,发现虽然WHERE条件列已有索

导读

执行UPDATE时,WEHRE条件列虽已有索引,但还会锁全表,肿么回事?

问题描述

叶师傅有次上课过程中执行UPDATE测试案例时,发现虽然WHERE条件列已有索引,有时候能利用二级索引进行更新(且只锁定相应必要的几行记录),但有时候却变成了根据主键进行更新,且会锁全表。我们先来看看下面的例子。

测试表 t1

CREATE TABLE `t1` (
 `c1` int(10) unsigned NOT NULL DEFAULT '0',
 `c2` int(10) unsigned NOT NULL DEFAULT '0',
 `c3` int(10) unsigned NOT NULL DEFAULT '0',
 `c4` int(10) unsigned NOT NULL DEFAULT '0',
 PRIMARY KEY (`c1`),
 KEY `c2` (`c2`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

表中数据

+----+----+----+----+
| c1 | c2 | c3 | c4 |
+----+----+----+----+
|  0 |  0 |  0 |  0 |
|  1 |  1 |  1 |  0 |
|  3 |  3 |  3 |  0 |
|  4 |  2 |  2 |  0 |
|  6 |  8 |  5 |  0 |
|  7 |  6 |  6 | 10 |
| 10 | 10 |  4 |  0 |
+----+----+----+----+

case1:根据二级索引UPDATE,不锁全表

  • 先看执行计划

yejr@imysql.com [yejr]>desc update t1 set c4=123 where c2>=8\G
*************************** 1. row ***************************
          id: 1
 select_type: UPDATE
       table: t1
  partitions: NULL
        type: range
possible_keys: c2
         key: c2
     key_len: 4
         ref: const
        rows: 2
    filtered: 100.00
       Extra: Using where
  • 启动两个session执行UPDATE测试

session1 session 2
mysql> begin; 
mysql> update t1 set c4=123 where c2>=8;

Query OK, 2 rows affected (0.00 sec)
Rows matched: 2 Changed: 2 Warnings: 0


mysql> begin;
mysql> select * from t1 where c2 = 7 for update;

...
1 row in set (0.00 sec)
#直接可得到结果,不会被阻塞

case2:根据PRIMARY KEY更新,锁全表

yejr@imysql.com [yejr]>desc update t1 set c4=123 where c2>=6\G
*************************** 1. row ***************************
          id: 1
 select_type: UPDATE
       table: t1
  partitions: NULL
        type: index
possible_keys: c2
         key: PRIMARY
     key_len: 4
         ref: NULL
        rows: 7
    filtered: 100.00
       Extra: Using where

我们能看到本次执行计划是 根据主键索引进行更新,且会锁全表

  • 同样地,启动两个session执行UPDATE测试

session1 session 2
mysql> begin; 
mysql> update t1 set c4=123 where c2>=6;

Query OK, 3 rows affected (0.00 sec)
Rows matched: 3 Changed: 3 Warnings: 0


mysql> begin;
mysql> select * from t1 where c2 = 3 for update;
#无法得到结果,被阻塞了
mysql> rollback; 
#执行rollback,释放锁


... 
1 row in set (4.23 sec) 
#session1释放锁后才能得到结果

查看行锁等待情况

yejr@imysql.com [yejr]>select * from sys.innodb_lock_waits\G
*************************** 1. row ***************************
               wait_started: 2017-08-15 15:20:20
                   wait_age: 00:00:17
              wait_age_secs: 17
               locked_table: `yejr`.`t1`
               locked_index: PRIMARY  <--主键上加锁
                locked_type: RECORD
             waiting_trx_id: 268350
        waiting_trx_started: 2017-08-15 15:20:20
            waiting_trx_age: 00:00:17
    waiting_trx_rows_locked: 2
  waiting_trx_rows_modified: 0
                waiting_pid: 13
              waiting_query: select * from t1 where c2 = 3 for update
            waiting_lock_id: 268350:387:3:4
          waiting_lock_mode: X
            blocking_trx_id: 268349
               blocking_pid: 12
             blocking_query:  NULL
           blocking_lock_id: 268349:387:3:4
         blocking_lock_mode: X
       blocking_trx_started: 2017-08-15 15:20:18
           blocking_trx_age: 00:00:19
   blocking_trx_rows_locked: 8  <-- 所有记录都被加锁了
 blocking_trx_rows_modified: 3  <---持有锁的事务更新了3行记录
    sql_kill_blocking_query: KILL QUERY 12
sql_kill_blocking_connection: KILL 12

问题分析

好了,案例说完了,也该说原因了。

肾好的同学可能记得我说过一个结论:当MySQL预估扫描行数超过全表总数约 20% ~ 30% 时,即便有二级索引,也会直接升级为全表扫描

这个结论的原因并不难理解,二级索引的顺序和主键顺序一般来说是不一样的,根据二级索引的顺序回表读数据时,实际上对于主键则很可能是随机扫描,因此当需要随机扫描的数量超过一定比例时(一般是20% ~ 30%),则优化器会决定直接改成全表扫描

上述说法出处:WHERE Clause Optimization(https://dev.mysql.com/doc/refman/5.7/en/where-optimization.html

Each table index is queried, and the best index is used unless the optimizer believes that it is more efficient to use a table scan. At one time, a scan was used based on whether the best index spanned more than 30% of the table, but a fixed percentage no longer determines the choice between using an index or a scan. The optimizer now is more complex and bases its estimate on additional factors such as table size, number of rows, and I/O block size.

不过,上面这个结论是针对读数据的情况,UPDATE/DELETE修改数据时是否也这样呢?

答案是肯定的,要不然上面的测试结果怎么解释......

按照官方开发者的说法,当优化器评估根据二级索引更新行数超过约50%(从上面测试结果来看,其实20% ~ 30%就已经是这样了,不过这个比例并不是固定值,会根据各种代价计算自动调整)就会改成走主键索引,并且锁全表,这么做是既定的策略,原因和上面一样,也是为了提高检索效率。

总结

老调重弹,几点建议:

  • 不管检索还是更新,都尽可能利用索引;

  • 不要一次性检索或更新大批量数据,建议分城多批次;

  • 事务尽快提交,降低行锁持有时间及其影响。

本文提到的问题,我也提了个BUG#87021,有兴趣的可以去看看。

延伸阅读

  • UTF8字符集的表怎么直接转UTF8MB4?

  • 细说ANALYZE TABLE

  • 优化系列 | DELETE子查询改写优化

  • [MySQL优化案例]系列 — 分页优化

  • 曝!和叶师傅**的正确姿势


知识无界限,不再加原创

喜欢就转走,铁粉加密圈

640?wx_fmt=png


好铁观音尽在

「老叶茶馆」

http://yejinrong.com

640?wx_fmt=png


推荐阅读
  • 微软头条实习生分享深度学习自学指南
    本文介绍了一位微软头条实习生自学深度学习的经验分享,包括学习资源推荐、重要基础知识的学习要点等。作者强调了学好Python和数学基础的重要性,并提供了一些建议。 ... [详细]
  • CSS3选择器的使用方法详解,提高Web开发效率和精准度
    本文详细介绍了CSS3新增的选择器方法,包括属性选择器的使用。通过CSS3选择器,可以提高Web开发的效率和精准度,使得查找元素更加方便和快捷。同时,本文还对属性选择器的各种用法进行了详细解释,并给出了相应的代码示例。通过学习本文,读者可以更好地掌握CSS3选择器的使用方法,提升自己的Web开发能力。 ... [详细]
  • 本文介绍了一个在线急等问题解决方法,即如何统计数据库中某个字段下的所有数据,并将结果显示在文本框里。作者提到了自己是一个菜鸟,希望能够得到帮助。作者使用的是ACCESS数据库,并且给出了一个例子,希望得到的结果是560。作者还提到自己已经尝试了使用"select sum(字段2) from 表名"的语句,得到的结果是650,但不知道如何得到560。希望能够得到解决方案。 ... [详细]
  • 在Xamarin XAML语言中如何在页面级别构建ControlTemplate控件模板
    本文介绍了在Xamarin XAML语言中如何在页面级别构建ControlTemplate控件模板的方法和步骤,包括将ResourceDictionary添加到页面中以及在ResourceDictionary中实现模板的构建。通过本文的阅读,读者可以了解到在Xamarin XAML语言中构建控件模板的具体操作步骤和语法形式。 ... [详细]
  • 本文讨论了如何使用IF函数从基于有限输入列表的有限输出列表中获取输出,并提出了是否有更快/更有效的执行代码的方法。作者希望了解是否有办法缩短代码,并从自我开发的角度来看是否有更好的方法。提供的代码可以按原样工作,但作者想知道是否有更好的方法来执行这样的任务。 ... [详细]
  • MyBatis多表查询与动态SQL使用
    本文介绍了MyBatis多表查询与动态SQL的使用方法,包括一对一查询和一对多查询。同时还介绍了动态SQL的使用,包括if标签、trim标签、where标签、set标签和foreach标签的用法。文章还提供了相关的配置信息和示例代码。 ... [详细]
  • 本文介绍了Python爬虫技术基础篇面向对象高级编程(中)中的多重继承概念。通过继承,子类可以扩展父类的功能。文章以动物类层次的设计为例,讨论了按照不同分类方式设计类层次的复杂性和多重继承的优势。最后给出了哺乳动物和鸟类的设计示例,以及能跑、能飞、宠物类和非宠物类的增加对类数量的影响。 ... [详细]
  • 欢乐的票圈重构之旅——RecyclerView的头尾布局增加
    项目重构的Git地址:https:github.comrazerdpFriendCircletreemain-dev项目同步更新的文集:http:www.jianshu.comno ... [详细]
  • 本文介绍了使用Spark实现低配版高斯朴素贝叶斯模型的原因和原理。随着数据量的增大,单机上运行高斯朴素贝叶斯模型会变得很慢,因此考虑使用Spark来加速运行。然而,Spark的MLlib并没有实现高斯朴素贝叶斯模型,因此需要自己动手实现。文章还介绍了朴素贝叶斯的原理和公式,并对具有多个特征和类别的模型进行了讨论。最后,作者总结了实现低配版高斯朴素贝叶斯模型的步骤。 ... [详细]
  • 十大经典排序算法动图演示+Python实现
    本文介绍了十大经典排序算法的原理、演示和Python实现。排序算法分为内部排序和外部排序,常见的内部排序算法有插入排序、希尔排序、选择排序、冒泡排序、归并排序、快速排序、堆排序、基数排序等。文章还解释了时间复杂度和稳定性的概念,并提供了相关的名词解释。 ... [详细]
  • Android日历提醒软件开源项目分享及使用教程
    本文介绍了一款名为Android日历提醒软件的开源项目,作者分享了该项目的代码和使用教程,并提供了GitHub项目地址。文章详细介绍了该软件的主界面风格、日程信息的分类查看功能,以及添加日程提醒和查看详情的界面。同时,作者还提醒了读者在使用过程中可能遇到的Android6.0权限问题,并提供了解决方法。 ... [详细]
  • 本文介绍了贝叶斯垃圾邮件分类的机器学习代码,代码来源于https://www.cnblogs.com/huangyc/p/10327209.html,并对代码进行了简介。朴素贝叶斯分类器训练函数包括求p(Ci)和基于词汇表的p(w|Ci)。 ... [详细]
  • 本文介绍了如何在MySQL中将零值替换为先前的非零值的方法,包括使用内联查询和更新查询。同时还提供了选择正确值的方法。 ... [详细]
  • 本文介绍了[从头学数学]中第101节关于比例的相关问题的研究和修炼过程。主要内容包括[机器小伟]和[工程师阿伟]一起研究比例的相关问题,并给出了一个求比例的函数scale的实现。 ... [详细]
  • 怎么在PHP项目中实现一个HTTP断点续传功能发布时间:2021-01-1916:26:06来源:亿速云阅读:96作者:Le ... [详细]
author-avatar
兴霖先玲广_215
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有