Mysql中的锁(case篇)

这篇具有很好参考价值的文章主要介绍了Mysql中的锁(case篇)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

case1(表锁的读-写-读阻塞)

上篇文档中提到过

WRITE locks normally have higher priority than READ locks to ensure that updates are processed as soon as possible. This means that if one session obtains a READ lock and then another session requests a WRITE lock, subsequent READ lock requests wait until the session that requested the WRITE lock has obtained the lock and released it.
对于读-写-读的情况,由于锁的优先级较高,如果申请写的session迟迟获取不到锁,会阻塞后续其他session申请读锁;

先看正常情况,表锁的读锁是可以加多个的,如下,通过两个查询命令也可以看到确实同时加上了,没有阻塞;

//console1
lock tables simple read;
//console2
lock tables simple read;

select * from performance_schema.metadata_locks;

Mysql中的锁(case篇)

show OPEN TABLES where In_use > 0;
Mysql中的锁(case篇)

但是在两次读中间插入一次写锁的获取,后面的读锁也会同时被阻塞

//console1
lock tables simple read;
//console2
lock tables simple write;//被console1阻塞
//console3
lock tables simple read;//被console2阻塞

实验证明确实如文档所说,原理还在研究中...

case2(元数据锁读-写-读)

mysql45讲中提到的一个问题,具体分析见mysql MDL读写锁阻塞,以及online ddl造成的“插队”现象_花落的速度的博客-CSDN博客
Mysql中的锁(case篇)

case3(next-key lock 和 primary key)

在分析之前,先贴一下45讲的总结,该总结版本是 5.x 系列 <=5.7.24,8.0 系列 <=8.0.13,而我测试的版本是8.0.33
Mysql中的锁(case篇)

原则 1:加锁的基本单位是 next-key lock。希望你还记得,next-key lock 是前开后闭区间。
原则 2:查找过程中访问到的对象才会加锁。
优化 1:索引上的等值查询,给唯一索引加锁的时候,next-key lock 退化为行锁。
优化 2:索引上的等值查询,向右遍历时且最后一个值不满足等值条件的时候,next-key lock 退化为间隙锁。
一个 bug:唯一索引上的范围查询会访问到不满足条件的第一个值为止。

目前的数据

CREATE TABLE `simple` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
  `name` varchar(256) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '字符',
  `seq` bigint NOT NULL COMMENT '消息序号',
  `type` tinyint NOT NULL COMMENT '类型,tinyint值',
  `version` int NOT NULL DEFAULT '1' COMMENT '版本值',
  `msg` text COLLATE utf8mb4_bin COMMENT '消息',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
  `yn` tinyint NOT NULL DEFAULT '1' COMMENT '是否有效',
  `uni` int NOT NULL COMMENT '唯一索引',
  PRIMARY KEY (`id`),
  UNIQUE KEY `unidx` (`uni`),
  KEY `seqidx` (`seq`)
) ENGINE=InnoDB AUTO_INCREMENT=301 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='简单测试表'

Mysql中的锁(case篇)

单一查询且查询结果存在(id=15)

存在一个意向表锁和行级读锁,理论上锁住的应该是(5, 15]这部分,但是由于是主键索引(唯一),所以只会锁15这一行,没有必要锁前面的间隙;这是优化1的体现;
LOCK_MODE为S,REC_NOT_GAP,我理解应该是说只有行锁,行锁类型是读锁;

start transaction ;
select * from simple where id = 15 lock in share mode ;
select * from performance_schema.data_locks;

Mysql中的锁(case篇)

单一查询且结果不存在(id=16)

将查询条件从15换成了16,理论上锁住的是(15,20]这部分,但是实验表明,20这行不会加行锁,所以最终表现为(15,20);这是优化2的体现;
LOCK_MODE为S,GAP,我理解应该是说只有间隙锁,即(15,20);

start transaction ;
select * from simple where id = 16 lock in share mode ;
select * from performance_schema.data_locks;
//console2
start transaction;
insert into simple (id,name,type,seq) value (16,5,5,5);//会被阻塞
select * from simple where id=20 for update ;//发现这行可以执行成功

既然可以成功,那就证明id = 16 的查询并没有锁20这一行,不然不可能加的上写锁

Mysql中的锁(case篇)

console2执行id=20后的结果

Mysql中的锁(case篇)

那这里如果我把id为20的更新成id为16会怎样?

update simple set id=16 where id=20;

经实验16-19都不能更新,20以后可以更,比如update simple set id=21 where id=20就可以成功;所以间隙锁是不是也能防止更新;又或者说,其实是因为更新的本质是删除再插入,再插入的被阻塞了,这里感兴趣的可以研究一下;

id>5

按照理论,应该锁住的后5往后的所有范围,即(5,15],(15,20],(20,23],(23,super..];
所以我推测LOCK_MODE只有一个S,代表加的是临键锁,类型是读锁,没有特殊表明缺少行锁或者间隙锁就是完整的临建锁,并且我在console2尝试插入id为6或者36的,都会被阻塞

//console1
start transaction ;
select * from simple where id>5 lock in share mode ;
select * from performance_schema.data_locks;
//console2
都会被阻塞
insert into simple (id,name,type,seq) value (6,5,5,5);
insert into simple (id,name,type,seq) value (36,5,5,5);

Mysql中的锁(case篇)

id>=5

和上面的唯一区别就是多了个等于5,那么5上是临键锁还是行锁呢?我觉得是行锁,因为优化1,而且这样和我们的认知也是比较符合的;
实际看到确实是这样;

start transaction ;
select * from simple where id>=5 lock in share mode ;
select * from performance_schema.data_locks;

Mysql中的锁(case篇)

id>5 and id<20

首先5<x<20,那么正常情况应该是(5,15]和(15,20],然后20因为不等于会被优化(触发了优化2),所以是(5,20)

start transaction ;
select * from simple where id>5 and id<20 lock in share mode ;
select * from performance_schema.data_locks;

Mysql中的锁(case篇)

id>5 and id<=20

假如是5<x<=20,那就会是(5,20];
但是注意我们前面提到过一个bug,可是我们看到目前就是锁到20为止,并不是(5,23),翻看评论区说在MySQL 8.0.18 已经修复,而我的版本是8.0.33,这里难道是修复了吗?先存疑,因为这里只能证明主键索引修复了,后面唯一索引那里还是乱的一批
Mysql中的锁(case篇)

id>30

应该会直接锁(23,super...)
Mysql中的锁(case篇)

case4(next-key lock和 unique key)

和case3唯一的区别就是将主键索引换成了唯一索引,猜测应该是一模一样的,因为文档里的特殊规则说的也都是唯一索引,而没有限制到主键上;

单一查询且查询结果存在(uni=15)

start transaction ;
select * from simple where uni = 15 lock in share mode ;
select * from performance_schema.data_locks;

Mysql中的锁(case篇)

理想很美好,现实很骨感;这是什么??突然想到行锁和间隙锁都是锁在索引上的锁,由于我查询结果是所有字段,所以会发生回表查询;当命中到唯一索引的时候会锁一次,然后根据主键id再锁一次;
但是现在我的uni和id字段值是一样的,所以为了区分,我将uni这一列都加了100,然后执行下面的句子

start transaction ;
select * from simple where uni = 115 lock in share mode ;
select * from performance_schema.data_locks;

Mysql中的锁(case篇)

可以看到primary那行应该是因为回表操作,而unidx那行应该则是对应唯一索引的查询,实际锁的范围逻辑和主键索引是一致的,只不过锁的内容我不理解,lock_data为115,15,为什么?
select id from simple where uni = 115 lock in share mode ;
而且如果我们查询的不是select *,而是select id ,锁的信息就不包含primary那行了;
Mysql中的锁(case篇)

单一查询且结果不存在(uni=116)

start transaction ;
select * from simple where uni = 116 lock in share mode ;
select * from performance_schema.data_locks;

Mysql中的锁(case篇)

由于查询不到,所以也不会回表查询,就不存在primary那行了

uni>105

start transaction ;
select id from simple where simple.uni>105 lock in share mode ;
select * from performance_schema.data_locks;

Mysql中的锁(case篇)

我理解到每个索引节点的时候,都会执行一次select * from simple where id = x;所以会多出几行只有行锁primary的记录;

uni>=105只是会在unidx和primary上各多一个锁,但范围和唯一索引逻辑依然一致,就不贴了

uni>105 and uni<120

//console1
commit ;
start transaction ;
select * from simple where uni>105 and uni<120 lock in share mode ;
select * from performance_schema.data_locks;
//console2
select * from simple where uni=120 for update ;//被阻塞

Mysql中的锁(case篇)

这里和上面不一样的是,这里把120这行也锁上了,主键索引锁20是间隙锁,这里是临键锁;为什么这里会锁上呢?就很像是bug并没有修复,依然锁到了第一个不满足条件的,并且加了临键锁

uni>105 and uni<=120

start transaction ;
select * from simple where uni>105 and uni<=120 lock in share mode ;
select * from performance_schema.data_locks;

Mysql中的锁(case篇)

这里更离谱,这里为什么把123都给锁上了??感觉bug依然存在,多锁了一个区间

uni>130和上面的id>30结果一样,就不贴了


总结:对于唯一索引来说,因为存在主键,那么会产生回表操作,回表操作会给主键再加一把锁;而那个bug依旧存在,只有主键索引的修复了,非主键唯一索引依然存在这个bug;

case5(索引加在哪)

Mysql中的锁(case篇)

//console1
start transaction ;
select id from simple where  uni=105 lock in share mode ;
select * from performance_schema.data_locks;
//console2
start transaction ;
update simple set name='new' where id=5;

现在我们已经清楚,执行完console1之后,会给unidx加一个行锁,因为没有回表,所以主键上没有锁;那么console2能否成功执行呢?

答案是
可以的;

我个人理解,是因为锁是加在索引上的,而索引是列维度的,不是行维度的;console2执行语句只会去判断id这个索引上,有没有5这个锁;
接下来我们反过来

//console1
start transaction ;
select * from simple where  id=5 lock in share mode ;
select * from performance_schema.data_locks;
//console2
start transaction ;
update simple set name='new' where uni=105;

你试着一起敲一下就会发现,咦,console2怎么阻塞了呢?按上面所说的,不是不应该吗?
实际上console1的执行锁的确实是id;
但是你console2的执行,会回表啊,会尝试给id加写锁,但是id已经加了读锁了,所以自然不行了;
所以,不要盲目的只看查询条件,要理解当前语句都会加什么锁,是否和已经加的锁冲突;
最后,我们再来看一个附加题,下面两个语句加的锁是否一样呢?

start transaction ;
select id from simple where  uni=105 lock in share mode ;
select * from performance_schema.data_locks;

start transaction ;
select id from simple where  uni=105 for update ;
select * from performance_schema.data_locks;

在我没有尝试之前,我理解都没有回表,那么就应该一个是唯一索引加读锁,一个是唯一索引加写锁;
但是实际结果却是lock in share mode是对的,for update会认为你要更新语句,自动给主键加锁了
Mysql中的锁(case篇)

case6(next-key lock 和index)

吸取uni的教训,我给seq的值都加了200,现在这个表是这样的
Mysql中的锁(case篇)

seq=215

start transaction ;
select * from simple where  seq=215 lock in share mode ;
select * from performance_schema.data_locks;

Mysql中的锁(case篇)

除了意向锁,其他三个我们一个个看;
seqidx(S)这行是普通索引执行时加的临键锁,由于不是唯一索引,所以不能优化(因为可能存在重复)
primary(S,REC_NOT_GAP)这是回表操作带来的
seqidx(S,GAP)这行是因为不是唯一索引,所以在查询到匹配的值之后不会立马停止(因为后面可能还存在相同的值),所以必须要到不符合条件的值为止,而所有查询过的都会加索引,所以存在一个间隙锁。

seq=216

start transaction ;
select * from simple where  seq=216 lock in share mode ;
select * from performance_schema.data_locks;

Mysql中的锁(case篇)

我理解,应该是从205开始查,查到第一个不符合条件的值是215,加上中间没有回表,所以就这一个锁;理论应该是(215,220],但由于优化2,所以退化为间隙锁;

seq>215 and seq<220

start transaction ;
select * from simple where  seq>215 and seq <220 lock in share mode ;
select * from performance_schema.data_locks;

Mysql中的锁(case篇)

从215开始匹配,第一个不符合条件的是220,所以只能是(215,220]

seq>215 and seq <=220

start transaction ;
select * from simple where  seq>215 and seq <=220 lock in share mode ;
select * from performance_schema.data_locks;

Mysql中的锁(case篇)

这里和上面区别就是不符合条件的会到223为止,另外中间因为匹配成功会回一次表
seq>230和前面unidx>130和id>30都一样

case7(next-key和没有索引)

alter table simple drop index  seqidx;
start transaction ;
select * from simple where  seq=215 lock in share mode ;
select * from performance_schema.data_locks;

前面提到过,查询条件匹配不到索引或者只是索引的一部分,这个时候为了保证数据的准确性,会给整个表“加锁”,其实给表里所有的记录都加锁(这里我不知道描述的对不对,因为表锁!=所有记录加锁,虽然效果相似,但并不是一个东西).
Mysql中的锁(case篇)

Mysql中的锁(case篇)

同时因为这个表存在意向读锁,通过lock tables simple write 加写的表锁会冲突;

参考文档:

06 | 全局锁和表锁 :给表加个字段怎么有这么多阻碍?-极客时间
mysql MDL读写锁阻塞,以及online ddl造成的“插队”现象_花落的速度的博客-CSDN博客文章来源地址https://www.toymoban.com/news/detail-514482.html

到了这里,关于Mysql中的锁(case篇)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Mysql中的锁(理论篇)

    为什么在可重复读级别下,幻读没有产生? 回想一下在事务隔离级别那篇文章中,可串行化是通过什么保证的? 对操作的每一行记录加读锁、写锁和范围锁;任何其他事务都必须等待持有锁的事务释放锁之后才能进行操作; 而可重复读级别相比之下唯一少的就是范围锁,所

    2024年02月11日
    浏览(37)
  • MYSQL中的锁(面试难点重点)

    首先说一下 这个加锁是个啥子过程呢 我们拿一条记录举例,这个记录就放在这,没人操作它,他就没生成锁结构, 直到有个事务操作它了,然后给它才生成了个锁结构,锁结构两个参数 trx(生成该锁的事务) is_waiting(正在等待就是:true 没在等待就是 false) (锁里面很多参数 这里这

    2024年02月16日
    浏览(39)
  • MySQL中的锁机制详解

    事务的 隔离性 (隔离级别)是由锁来保证的。 并发访问数据的情况分为: 1.读-读 即并发事务相继读取相同的记录,因为没涉及到数据的更改,所以不会有并发安全问题,允许这种情况发生。 2.写-写 即并发事务对相同记录进行修改,会出现 脏写 问题,因为任何一种隔离级

    2024年02月06日
    浏览(27)
  • 【数据库】MySQL中的锁机制

    本系列包含: 【数据库】MySQL 的存储引擎 【数据库】B 树、B+ 树、索引 【数据库】从事务到锁机制 【数据库】MySQL 中的锁机制 数据库锁定机制简单来说,就是数据库为了保证数据的一致性,而使各种共享资源在被并发访问变得有序所设计的一种规则。 MySQL 数据库由于其自

    2024年02月04日
    浏览(41)
  • 究竟会不会阻塞?HTML文档渲染中的CSS和JS文件下载探秘

    在Web前端开发中,网页的性能优化一直是一个重要的课题。其中,优化网页的加载速度尤为关键。本文将探讨一个常见的问题:HTML文档渲染过程中,CSS文件和JS文件的下载是否会阻塞渲染,以及如何处理这个问题。 阻塞渲染的问题 在讨论之前,我们需要了解一些关键概念。

    2024年02月11日
    浏览(28)
  • MySQL中的SQL高级语句[一](上篇)

    使用语言  MySQL 使用工具  Navicat Premium 16 代码能力快速提升小方法,看完代码自己敲一遍,十分有用 拖动表名到查询文件中就可以直接把名字拉进来 以下是使用脚本方法,也可以直接进行修改 中括号,就代表可写可不写  目录 1.修改数据表结构 1.1 修改数据库的表名  1.

    2024年04月14日
    浏览(39)
  • Java - JUC(java.util.concurrent)包详解,其下的锁、安全集合类、线程池相关、线程创建相关和线程辅助类、阻塞队列

    JUC是java.util.concurrent包的简称,在Java5.0添加,目的就是为了更好的支持高并发任务。让开发者进行多线程编程时减少竞争条件和死锁的问题 java.lang.Thread.State tools(工具类):又叫信号量三组工具类,包含有 CountDownLatch(闭锁) 是一个同步辅助类,在完成一组正在其他线程中

    2024年02月05日
    浏览(29)
  • Mysql表锁与行锁

    锁是计算机协调多个进程或线程并发访问某一资源的机制。在数据库中,除传统的计算资源(CPU、RAM、I/O)的争用以外,数据也是 一种供许多用户共享的资源。如何保证数据并发访问的一致性、有效性是所有数据库必须解决的一个问题,锁冲突也是影响数据库并发访问性能的一

    2024年02月16日
    浏览(32)
  • MySQL 全局锁、表锁和行锁

    1、全局锁 全局锁,是指对整个MySQL数据库加锁,对应的命令是flush tables with read lock;(以下简称FTWRL) 当你需要让整个库处于只读模式的时候,可以使用这个语法,它的应用场景,一般是在 全库逻辑备份的时候 。我们知道MySQL自带的mysqldump逻辑备份工具可以使用--single-tran

    2024年02月08日
    浏览(28)
  • MySQL中锁的简介——表级锁-表锁

    2024年02月15日
    浏览(29)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包