重重封锁,让你一条数据都拿不到《死磕MySQL系列 十三》
在开发中有遇到很简单的SQL却执行的非常慢,甚至只查询一行数据。
咔咔遇到的只有两种情况,一种是MySQL服务器CPU占用率很高,所有的SQL都执行的很慢直到超时,程序也直接502,另一种情况是行锁造成的锁等待。
接下来咔咔带领大家看看各种为难SQL执行的场景,本期文章带大家再熟悉一下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系列 十三》的更多相关文章
- 什么?还在用delete删除数据《死磕MySQL系列 九》
系列文章 五.如何选择普通索引和唯一索引<死磕MySQL系列 五> 六.五分钟,让你明白MySQL是怎么选择索引<死磕MySQL系列 六> 七.字符串可以这样加索引,你知吗?& ...
- 原来一条select语句在MySQL是这样执行的《死磕MySQL系列 一》
前言 看到蒋老师的第一篇文章后就收货颇丰,真是句句戳中痛点. 令我记忆最深的就是为什么知道了一个个技术点,却还是用不好 ?不管是蒋老师所说的Redis还是本系列要展开学习的MySQL. 这是一个值得思 ...
- QTreeView处理大量数据(使用1000万条数据,每次都只是部分刷新)
如何使QTreeView快速显示1000万条数据,并且内存占用量少呢?这个问题困扰我很久,在网上找了好多相关资料,都没有找到合理的解决方案,今天在这里把我的解决方案提供给朋友们,供大家相互学习. 我开 ...
- Spark会把数据都载入到内存么
转载自:https://www.iteblog.com/archives/1648 前言: 很多初学者其实对于Spark的编程模式还是RDD这个概念理解不到位,就会产生一些误解.比如,很多时候我们常常 ...
- Oracle的trunc和dbms_random.value随机取n条数据
今天在review项目代码的时候看到这样一个问题,有一张号码表,每次需要从这样表中随机取6个空闲的号码,也就是每次取出来的6个号码应该都会有所不同.然后我就看到了这样的SQL select t.* ...
- Spark会把数据都载入到内存么?
前言 很多初学者其实对Spark的编程模式还是RDD这个概念理解不到位,就会产生一些误解. 比如,很多时候我们常常以为一个文件是会被完整读入到内存,然后做各种变换,这很可能是受两个概念的误导: RDD ...
- MongoDB 倾向于将数据都放在一个 Collection 下吗?
不是这样的. Collection 的单个 doc 有大小上限,现在是 16MB,这就使得你不可能把所有东西都揉到一个 collection 里.而且如果 collection 结构过于复杂,既会影响 ...
- mysql使用——sql实现随机取一条数据
最近在做接口测试的时候,测试数据是从数据库查询的,但是当需要并发多次去调用接口时,如果sql只是单纯的进行了limit取值,那并发的时候肯定会每条数据都一样. 因此,研究了下sql随机取一条数据的写法 ...
- 1.(group by)如何让group by分组后,每组中的所有数据都显示出来
问题描述:表如下,如何让这个表按device_id这个字段分组,且组中的每条数据都查寻出来?(假如说这个表名为:devicedata) 错误答案:select * from devicedata GR ...
随机推荐
- 巩固java第五天
巩固内容: HTML 实例解析 <p> 元素: <p>这是第一个段落.</p> 这个 <p> 元素定义了 HTML 文档中的一个段落. 这个元素拥有一个 ...
- 生产调优4 HDFS-集群扩容及缩容(含服务器间数据均衡)
目录 HDFS-集群扩容及缩容 添加白名单 配置白名单的步骤 二次配置白名单 增加新服务器 需求 环境准备 服役新节点具体步骤 问题1 服务器间数据均衡 问题2 105是怎么关联到集群的 服务器间数据 ...
- MPI 学习笔记
目录 MPI学习笔记 MPI准备 概述 前置知识补充 环境部署 1.修改IP及主机名 2.关闭防火墙 3.实现免密码SSH登录 4.配置MPI运行环境 5.测试 程序的执行 编译语句 运行语句 MPI ...
- 运算符重载+日期类Date
Hello,一只爱学习的鱼 大学学习C++运算符重载的时候,老师出了一道"运算符重载+类"的综合练习题,让我们来一起看看吧! 题目: 设计一个日期类Date,包括年.月.日等私有成 ...
- 如何在 ASP.NET Core 中构建轻量级服务
在 ASP.NET Core 中处理 Web 应用程序时,我们可能经常希望构建轻量级服务,也就是没有模板或控制器类的服务. 轻量级服务可以降低资源消耗,而且能够提高性能.我们可以在 Startup 或 ...
- 从for循环到机器码
def p(*x): print(x) p(type(range), dir(range)) r = range(2); i = iter(r) try: p(next(i)); p(next(i)) ...
- Spark(三)【RDD中的自定义排序】
在RDD中默认的算子sortBy,sortByKey只能真的值类型数据升序或者降序 现需要对自定义对象进行自定义排序. 一组Person对象 /** * Person 样例类 * @param nam ...
- 商业爬虫学习笔记day6
一. 正则解析数据 解析百度新闻中每个新闻的title,url,检查每个新闻的源码可知道,其title和url都位于<a></a>标签中,因为里面参数的具体形式不一样,同一个正 ...
- Dubbo应用到web工程
一.创建提供者03-provider-web (1) 创建工程 创建Maven的web工程,然后创建实现类. (2) 导入依赖 Spring的版本为4.3.16 需要的依赖有: dubbo2.7.0版 ...
- SpringBoot+MybatisPlus实现批量添加的两种方式
第一种: 因为Mysql数据每次发送sql语句的长度不能超过1M,所以,每次发送insert语句以固定长度发送: 将sql语句在provider中,以固定长度装入List集合中,然后返回service ...