在开发中有遇到很简单的SQL却执行的非常慢,甚至只查询一行数据。

咔咔遇到的只有两种情况,一种是MySQL服务器CPU占用率很高,所有的SQL都执行的很慢直到超时,程序也直接502,另一种情况是行锁造成的锁等待。

接下来咔咔带领大家看看各种为难SQL执行的场景,本期文章带大家再熟悉一下MySQL中的锁

最新文章

死磕MySQL系列总目录

什么?还在用delete删除数据《死磕MySQL系列 九》

MySQL统计总数就用count(*),别花里胡哨的《死磕MySQL系列 十》

为什么MySQL字符串不加引号索引失效?《死磕MySQL系列 十一》

打开order by的大门,一探究竟《死磕MySQL系列 十二》

一、MDL锁

现在你应该知道要聊的是MDL,这个锁很少有开发人员去关注,在开发中并没有实际的语法来开启或关闭锁。

这个特性是在MySQL5.5引入的,目的是为了解决一张表同时在做查询和修改表结构,这种情况必定会造成查询结果跟表结构无法对应。

所以,当你访问一个表时会默认加上MDL锁,MDL锁的互斥关系跟共享锁、排它锁是一样的,读写互斥,写写互斥。

MDl锁是在事务提交后才会释放,执行期间一直持有。

同时你需要知道MDL锁的操作会形成一个队列,队列中写锁获取优先级高于读锁,一旦出现MDL写锁等待,会阻塞后续该表的所有CURL操作。

也就说,一旦你在一个未提交事务之后执行了DDL操作,那么等到的结果就是MySQL挂掉,客户端会有重试机制,DDL后所有CURD会在超时后重新发起请求,这个库的线程会很快爆满。

当线程A通过DDL时手里握着表的MDL写锁,而线程B的查询需要获取MDL读锁,所以线程B就一直处于锁等待状态。

在生产环境是坚决不可以直接修改表结构的,如果你的表非常大的话会很容易造成业务所有的CURD处于堵塞。

解决方案

大表DDL可以使用pt-online-schema-change这个工具来处理,具体怎么用后续文章会跟大家分享出来。

若不小心在线上执行了修改表结构,可以通过show processlist命令来查找,不过这个命令在查找上很不方便,可以使用performance_schema和sys系统库来进行查询。前提是你的MySQL参数performance_schema=on,在MySQL8.0.26版本中,这个参数是默认开启的,若你所在的版本没有开启时可以打开。

然后就可以执行select blocking_pid from sys.schema_table_lock_waits,就可以看到当前持有MDL锁的线程ID,直接使用kill命令即可。

二、全局锁

在MySQL强人“锁”难《死磕MySQL系列 三》的文章中给大家聊到了全局锁,使用语法flush table t with read lock 或者 flush table with read lock

指定表名时就锁定指定表,未指定时表示锁定所有表。

这两个语句执行是非常快的,一般不会造成SQL堵塞,但防火、防盗你也防不住有其它线程的语句把flush语句堵塞住。

线程A执行大事物,需要执行10s

线程B执行flush table t with read lock

线程C执行select * from evt_sms where id = 1

所以线程C哪怕是只查询一条数据在10s内也是返回不了结果的,线程B的flush 命令需要等线程A的事务执行完毕,而线程C此时却被未执行的线程B堵塞着。

解决方案

一般出现这种情况只需要执行show processlist就可以看到堵塞线程C的线程是那个,同样直接使用kill掉对应的线程即可。

三、行锁

这个场景是非常好模拟的,接下来让我们一起看看

线程A正常修改大批量数据执行语句为update evt_sms set code = 123 where id > 11089

线程B执行select * from evt_sms where id = 120365 lock in share mode

在文章开头就跟大家简单的说了一句,MySQL中读锁与写锁、写锁与写锁互斥,所以线程B会一直等待线程A的事务提交之后才能返回结果。

解决方案

分析一下,线程B执行的语句添加的是读锁,能被堵住的只有是写锁,所以可以直接在sys.innodb_lock_waits表中查到占着这个写锁的是谁。

执行语句select * from evt_sms sys.innodb_lock_waits where lock_table='kaka.evt_sms'\G

这个试验就不演示了,复现过程也十分简答可以自己看一下哈!输出结果的最后一行就是解决方案,带着你的答案来到评论区

四、快照读引发的问题

了解过MVCC实现原理的大概率都会看到过当前读、快照读这两个词,如果你还不知道它们是什么就好好记一下。

当前读

执行select语句时加上共享锁、排它锁的操作就是当前读。

例:select * from evt_sms where id = 1 lock in share mode

这里的共享锁、排它锁也就是常说的读锁、写锁

在MySQL的Innodb存储引擎中进行DML操作时会默认添加排它锁

上边这个例子,select语句一旦加上了共享锁其它线程是不能修改当前记录的,因此当前读读取的数据库就是最新的数据

快照读

快照读的前提是隔离级别不是串行级别,串行级别的快照读会退化为当前读,快照读的出现是为了提高事务并发性,其实现也是基于MVCC的

MVCC在某种情况下可以认为是行锁的一个变种,但要知道的是在很多情况是不会有加锁行为的

这时你应该记住快照读获取的数据不是最新的,有可能是之前版本的数据

实现MVCC的三大因素隐式字段、undo log、read-view,read-view就是通过快照读产生的,它是由查询的那一时间所有未提交事务ID组成的数组,和已经创建的最大事务ID组成的。然后通过本线程的事务ID在read-view中进行对比

为什么说快照读会引发查询迟迟不返回结果

上文给大家提了一个东西undo log,都知道undo log是回滚日志,查询慢的原因也在这里

线程A先开启一个事务

线程B开启对id为1的数据行进行更新

由于id = 1的数据很多所以会产生很多的版本链,这里就认为是5万个

线程A执行了select * from evt_sms where id = 1就会迟迟返回不了结果

此时线程B并没有提交事务,所以线程A的查询需要根据版本链一直回退到5W个undo log之前,也就是这里导致查询非常慢

下图是一个咔咔之前做的undo log版本链图

线程A的查询是快照读,执行查询时会产生read-view,read-view会把线程A、线程B的事务存放在一个数组中,然后用一定的规则进行判断线程A能看到的数据是什么。

比对规则是什么

trx_id为当前的事务ID,min_id、max_id为当前启动事务的最大事务ID和最小事务ID

如果落在trx_id<min_id,表示此版本是已经提交的事务生成的,由于事务已经提交所以数据是可见的

如果落在trx_id>max_id,表示此版本是由将来启动的事务生成的,是肯定不可见的

若在min_id<=trx_id<=max_id时

如果row的trx_id在数组中,表示此版本是由还没提交的事务生成的,不可见,但是当前自己的事务是可见的
如果row的trx_id不在数组中,表明是提交的事务生成了该版本,可见
在这里还有一个特殊情况那就是对于已经删除的数据,在之前的undo log日志讲述时说了update和delete是同一种类型的undo log,同样也可以认为delete就是update的特殊情况。

当删除一条数据时会将版本链上最新的数据复制一份,然后将trx_id修改为删除时的trx_id,同时在该记录的头信息中存在一个delete flag标记,将这个标记写上true,用来表示当前记录已经删除。

在查询时按照版本链的规则查询到对应的记录,如果delete flag标记位为true,意味着数据已经被删除,则不返回数据。

五、总结

本期文章通过MDL锁、全局锁、行锁、undo log说明查询一条数据页迟迟不返回的问题,可以看到大多数都是一些理论知识,有些东西看着看着也就理解其中的含义了。

这里需要注意的是不要把MDL和DML搞混淆了,这可是两个东西,MDL指的是锁、而DML指的是数据库的增删改查。

坚持学习、坚持写作、坚持分享是咔咔从业以来所秉持的信念。愿文章在偌大的互联网上能给你带来一点帮助,我是咔咔,下期见。

重重封锁,让你一条数据都拿不到《死磕MySQL系列 十三》的更多相关文章

  1. 什么?还在用delete删除数据《死磕MySQL系列 九》

    系列文章 五.如何选择普通索引和唯一索引<死磕MySQL系列 五> 六.五分钟,让你明白MySQL是怎么选择索引<死磕MySQL系列 六> 七.字符串可以这样加索引,你知吗?& ...

  2. 原来一条select语句在MySQL是这样执行的《死磕MySQL系列 一》

    前言 看到蒋老师的第一篇文章后就收货颇丰,真是句句戳中痛点. 令我记忆最深的就是为什么知道了一个个技术点,却还是用不好 ?不管是蒋老师所说的Redis还是本系列要展开学习的MySQL. 这是一个值得思 ...

  3. QTreeView处理大量数据(使用1000万条数据,每次都只是部分刷新)

    如何使QTreeView快速显示1000万条数据,并且内存占用量少呢?这个问题困扰我很久,在网上找了好多相关资料,都没有找到合理的解决方案,今天在这里把我的解决方案提供给朋友们,供大家相互学习. 我开 ...

  4. Spark会把数据都载入到内存么

    转载自:https://www.iteblog.com/archives/1648 前言: 很多初学者其实对于Spark的编程模式还是RDD这个概念理解不到位,就会产生一些误解.比如,很多时候我们常常 ...

  5. Oracle的trunc和dbms_random.value随机取n条数据

    今天在review项目代码的时候看到这样一个问题,有一张号码表,每次需要从这样表中随机取6个空闲的号码,也就是每次取出来的6个号码应该都会有所不同.然后我就看到了这样的SQL select   t.* ...

  6. Spark会把数据都载入到内存么?

    前言 很多初学者其实对Spark的编程模式还是RDD这个概念理解不到位,就会产生一些误解. 比如,很多时候我们常常以为一个文件是会被完整读入到内存,然后做各种变换,这很可能是受两个概念的误导: RDD ...

  7. MongoDB 倾向于将数据都放在一个 Collection 下吗?

    不是这样的. Collection 的单个 doc 有大小上限,现在是 16MB,这就使得你不可能把所有东西都揉到一个 collection 里.而且如果 collection 结构过于复杂,既会影响 ...

  8. mysql使用——sql实现随机取一条数据

    最近在做接口测试的时候,测试数据是从数据库查询的,但是当需要并发多次去调用接口时,如果sql只是单纯的进行了limit取值,那并发的时候肯定会每条数据都一样. 因此,研究了下sql随机取一条数据的写法 ...

  9. 1.(group by)如何让group by分组后,每组中的所有数据都显示出来

    问题描述:表如下,如何让这个表按device_id这个字段分组,且组中的每条数据都查寻出来?(假如说这个表名为:devicedata) 错误答案:select * from devicedata GR ...

随机推荐

  1. Label -- 跳出循环的思路

    let num = 0 ; outPoint: //label for (let i = 0; i < 10; i++) { for ( let j = 0; j < 10; j++) { ...

  2. SpringCloud微服务实战——搭建企业级开发框架(三十二):代码生成器使用配置说明

    一.新建数据源配置 因考虑到多数据源问题,代码生成器作为一个通用的模块,后续可能会为其他工程生成代码,所以,这里不直接读取系统工程配置的数据源,而是让用户自己维护. 参数说明 数据源名称:用于查找区分 ...

  3. FileReader (三) - 网页拖拽并预显示图片简单实现

    以下是一个很贱很简单的一个 在网页上图拽图片并预显示的demo. 我是从https://developer.mozilla.org/en-US/docs/Web/API/FileReader#Stat ...

  4. centos7安装Docker详细步骤(无坑版教程)

    一.安装前必读 在安装 Docker 之前,先说一下配置,我这里是Centos7 Linux 内核:官方建议 3.10 以上,3.8以上貌似也可. 注意:本文的命令使用的是 root 用户登录执行,不 ...

  5. 转Android service 启动篇之 startForegroundService

    本文转自:https://blog.csdn.net/shift_wwx/article/details/82496447 前言: 在官方文档 Android 8.0 行为变更 中有这样一段话: An ...

  6. linux 6.5 网卡

    启动网卡 ifup eth0 eth0:网卡名称 设置网卡开机启动 vi /etc/sysconfig/network-scripts/ifcfg-eth0 ONBOOT=yes

  7. 增大Oracle Virtualbox的磁盘空间

    https://blog.csdn.net/hiyachen/article/details/102131823 背景 在virtualbox中装好Linux以及Application之后,发现硬盘空 ...

  8. spring jdbc 配置数据源连接数据库

    概述 在XML配置数据源,并将数据源注册到JDBC模板 JDBC模板关联业务增删改查 在XML配置数据源 <?xml version="1.0" encoding=" ...

  9. Webpack学习篇

    <深入浅出Webpack>优化篇 01 Webpack 优化可以分为开发优化和输出质量优化两部分,主要要点如下: 优化开发体验,提升开发效率 优化构建速度 优化使用体验 优化输出质量 减少 ...

  10. Nginx结构原理全解析

    目录 一.Nginx简单介绍 二.Nginx优势 IO多路复用epoll模型 轻量级 Nginx的基本功能 .Nginx应用场景 Nginx代理 三.Nginx的结构解析 进程操作 事件模型 事件处理 ...