简单了解 MySQL 中相关的锁
本文主要是带大家快速了解 InnoDB 中锁相关的知识
基础概念解析和RocketMQ详细的源码解析
http://xiazai.jb51.net/202105/yuanma/RocketMQ_jb51.rar
为什么需要加锁
首先,为什么要加锁?我想我不用多说了,想象接下来的场景你就能 GET 了。
你在商场的卫生间上厕所,此时你一定会做的操作是啥?锁门。如果不锁门,上厕所上着上着,啪一下门就被打开了,可能大概也许似乎貌似有那么一丁点的不太合适。
数据也是一样,在并发的场景下,如果不对数据加锁,会直接破坏数据的一致性,并且如果你的业务涉及到钱,那后果就更严重了。
锁门表情包
锁的分类
在 InnoDB 中,都有哪些锁?其实你应该已经知道了很多了,例如面试中会问你存储引擎 MyISAM 和 InnoDB 的区别,你会说 MyIASM 只有表锁,但是 InnoDB 同时支持行锁和表锁。你可能还会被问到乐观锁和悲观锁的区别是啥。
锁的概念、名词很多,如果你没有对锁构建出一个完整的世界观,那么你理解起来就会比较有阻碍,接下来我们把这些锁给分一下类。
按照锁的粒度
按照锁的粒度进行划分可以分为:
- 表锁
- 行锁
这里就不讨论页锁了,页锁是 BDB(BerkeleyDB) 存储引擎中才有的概念,我们这里主要讨论 InnoDB 存储引擎。
按照锁的思想
按照加锁的思想可以分为:
- 悲观锁
- 乐观锁
这里的悲观、乐观和你平时理解的名词是同一个意思。乐观锁认为大概率不会发生冲突,只在必要的时候加锁。而悲观锁认为大概率会冲突,所以无论是否必要加锁都会执行加锁操作。
按照兼容性
按照兼容性可以把锁划分为:
- 共享锁
- 排他锁
被加上共享锁的资源,能够和其他人进行共享,而如果被加上了排他锁,其他人在拿不到这把锁的情况下是无法进行任何操作的。
按照锁的实现
这里的实现就是 InnoDB 中具体的锁的种类了,分别有:
- 意向锁(Intention Locks)
- 记录锁(Record Locks)
- 间隙锁(Gap Locks)
- 临键锁(Next-Key Locks)
- 插入意向锁(Insert Intention Locks)
- 自增锁(AUTO-INC Locks)
即使按照这种分类来对锁进行了划分,看到了这么多的锁的名词可能仍然会有点懵。比如我SELECT ... FOR UPDATE
的时候到底加的是什么锁?
我们应该透过现象看本质,本质是什么?本质是锁到底加在了什么对象上,而这个很好回答:
- 加在了表上
- 加在了行上
而对于加在行上的锁,其本质又是什么?本质是将锁加在了索引上。
意向锁
在 InnoDB 中支持了不同粒度的锁,行锁和表锁。例如lock tables
命令就会持有对应表的排他锁。为了使多种不同粒度的锁更实用,InnoDB 设计了意向锁。
意向锁是一种表级锁,它表明了接下来的事务中,会使用哪种类型的锁,它有以下两种类型:
- 共享意向锁(IS) 表明该事务会打算对表中的记录加共享锁
- 独占意向锁(IX) 则是加排他锁
例如,select ... for share
就是加的共享意向锁,而SELECT .. FOR UPDATE
则是加的独占意向锁。其规则如下:
- 一个事务如果想要获取某张表中某行的共享锁,它必须先获取该表的共享意向锁,或者独占意向锁。
- 同理,如果想获取排他锁,它必须先获取独占意向锁
下图是这几种锁的组合下相互互斥、兼容的情况
对照上面的表,在相互兼容的情况下,对应的事务就能获取锁,但是如果不兼容则无法获取锁,直到不兼容的锁释放之后才能获取。
看到这里你可能就会有问题了,那既然意向锁除了 LOCK TBALES
之外什么都不阻塞。那我要它何用?
还是通过例子,假设事务 A 获取了 student 表中 id = 100 这行的共享锁,之后事务 B 需要申请 student 表的排他锁。而这两把锁明显是冲突的,而且还是对于同一行。
那 InnoDB 需要如何感知 A 获取了这把锁?遍历整个 B+ 树吗?不,答案就是意向锁。事务 B 申请写表的排他锁时,InnoDB 会发现事务 A 已经获取了该表的意向共享锁,说明 student 表中已经有记录被共享锁锁住了。此时就会阻塞住。
并且,意向锁除了像LOCK TABLES
这种操作之外,不会阻塞其他任何操作。换句话说,意向锁只会和表级别的锁之间发生冲突,而不会和行级锁发生冲突。因为意向锁的主要目的是为了表明有人即将、或者正在锁定某一行。
就像你去图书馆找书,你并不需要每个书架挨着挨着找,直接去服务台用电脑一搜,就知道图书馆有没有这本书。
记录锁
这就是记录锁,是行锁的一种。记录锁的锁定对象是对应那行数据所对应的索引。对索引不太清楚的可以看看这篇文章。
当我们执行SELECT * FROM student WHERE id = 1 FOR UPDATE
语句时,就会对值为1的索引加上记录锁。至于要是一张表里没有索引该怎么办?这个问题在上面提到的文章中也解释过了,当一张表没有定义主键时,InnoDB 会创建一个隐藏的RowID,并以此 RowID 来创建聚簇索引。后续的记录锁也会加到这个隐藏的聚簇索引上。
当我们开启一个事务去更新 id = 1 这行数据时,如果我们不马上提交事务,然后再启一个事务去更新 id = 1 的行,此时使用 show engine innodb status
查看,我们可以看到lock_mode X locks rec but not gap waiting
的字样。
X是排他锁的意思,从这可以看出来,记录锁其实也可以分为共享锁、排他锁模式。当我们使用FOR UPDATE
是排他,而使用LOCK IN SHARE MODE
则是共享。
而在上面字样中出现的 gap
就是另一种行锁的实现间隙锁。
间隙锁
对于间隙锁(Gap Locks)而言,其锁定的对象也是索引。为了更好的了解间隙锁,我们举个例子。
SELECT name FROM student WHERE age BETWEEN 18 AND 25 FOR UPDATE
您可能感兴趣的文章
- 05-31MySQL中的 inner join 和 left join的区别解析(小结果集驱动大结果集)
- 05-31MySQL索引失效十种场景与优化方案
- 05-31MYSQL 高级文本查询之regexp_like和REGEXP详解
- 05-31MySQL获取binlog的开始时间和结束时间(最新方法)
- 05-31MySQL索引查询的具体使用
- 05-31基于MySQL和Redis扣减库存的实践
- 05-31关于MySQL的存储过程与存储函数
- 05-31MySQL实战文章(非常全的基础入门类教程)
- 05-31MySQL Flink Watermark实现事件时间处理的关键技术
- 05-31MySQL Flink实时流处理的核心技术之窗口机制
阅读排行
推荐教程
- 05-30Navicat for MySQL 11注册码激活码汇总
- 05-27Mysql误删数据快速恢复
- 05-31VS2022连接数据库MySQL并进行基本的表的操作指南
- 05-30解决seata不能使用mysql8版本的问题方法
- 05-30MYSQL字符集设置的方法详解(终端的字符集)
- 05-30解决MySQL启动报错:ERROR 2003 (HY000): Can't con
- 05-30关于Mysql-connector-java驱动版本问题总结
- 11-22mac下安装mysql忘记密码的修改方法
- 05-30MySQL中的隐藏列的具体查看
- 11-22mysql exists与not exists实例详解