可以把锁定读的执行看成是依次读取若干个扫描区间中的记录
- 步骤一:首先快速地在B+树叶子节点中定位到该扫描区间总的第一条记录作为当前记录
- 步骤二:为当前记录加锁---
在隔离级别不大于READ COMMITTED时,会为当前记录加记录锁。在隔离级别不小于REPEATABLE READ 时,会为当前记录加next-key锁。
- 步骤三:判断索引下推的条件是否成立---
索引下推是把与被使用索引有关的搜索条件下推到存储引擎中判断,而不是返回server层再判断。索引下推只是为了减少回表次数,也就是减少读取完整的聚簇索引记录的次数,从而减少I/O操作。所以只适用二级索引,不适用于聚簇索引。索引下推仅适用于SELECT语句,不适用于UPDATE、DELETE这些需要改动记录的语句。
-
- 在存在索引条件下推的条件时,如果当前记录符合索引下推的条件,则跳到步骤四继续执行;如果不符合,则直接获取到当前记录所在单向链表的下一条记录,将该记录作为新的当前记录,并跳回步骤二。另外,步骤三还会判断当前记录是否符合形成扫描区间的边界条件,如果不符合,则跳过步骤四和步骤五,直接向server层返回一个”查询完毕“的信息。---这一步不会释放锁
- 步骤四:如果读取的是二级索引记录,则需要进行回表操作,获取到对应的聚簇索引记录并给该聚簇索引记录加记录锁
- 步骤五:判断边界条件是否成立--
如果该记录符合边界条件,则跳到步骤6执行,否则在隔离级别不大于READ COMMITTER时,就要释放掉加在该记录上的锁。在隔离级别不小于RR时,不释放加在该记录上的锁,并且向server层返回一个查询完毕的信息
- 步骤六:server层判断其他搜索条件是否成立--
除了索引下推中的条件以外,server层还需要判断其他搜索条件是否成立。如果成立,则将该记录发送到客户端,否则在隔离级别不大于READ COMMITTER时,就要释放掉加在该记录上的锁。在隔离级别不小于RR时,不释放加在该记录上的锁
- 步骤七:获取当前记录所在单向链表的下一条记录,并讲其作为新的当前记录,并跳回到步骤二
实例---如果走的是二级索引,存在索引下推的情况分析
SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0; -- ---------------------------- -- Table structure for hero -- ---------------------------- DROP TABLE IF EXISTS `hero`; CREATE TABLE `hero` ( `number` int NOT NULL, `name` varchar(45) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, `country` varchar(45) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, PRIMARY KEY (`number`) USING BTREE, INDEX `idx_name`(`name` ASC) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of hero -- ---------------------------- INSERT INTO `hero` VALUES (1, 'l刘备', '蜀'); INSERT INTO `hero` VALUES (3, 'z诸葛亮', '蜀'); INSERT INTO `hero` VALUES (8, 'c曹操', '魏'); INSERT INTO `hero` VALUES (15,'x荀彧', '魏'); INSERT INTO `hero` VALUES (20, 's孙权', '吴'); SET FOREIGN_KEY_CHECKS = 1;
SELECT * FROM hero FORCE INDEX(idx_name) WHERE name > 'c曹操' AND name <='x荀彧'AND country !='吴' LOCK IN SHARE MODE
在隔离级别不大于RC时的加锁情况
对name值为'l刘备'的二级索引记录进行分析
- 步骤一:读取在('c曹操','x荀彧']扫描区间的第一条二级索引记录,也就是name值为'l刘备'的二级索引记录
- 步骤二:为name值为'l刘备'的二级索引记录加S型记录锁
- 步骤三:'l刘备'符合索引下推的条件
- 步骤四:因为是二级索引记录,所以需要对记录执行回表操作,找到相应的聚簇索引记录,也就是number为1的聚簇索引记录,然后为该聚簇索引记录加上一个S型记录锁
- 步骤五:符合边界条件
- 步骤六:server层判断'l刘备'的二级索引符合条件country!='吴',然后发送给客户端,并且不释放加在该记录上的锁
- 步骤七:获取刘备所在单链表的下一条记录,也就是孙权的二级索引记录
对name值为's孙权'的二级索引记录进行分析
- 步骤二:加S型记录锁
- 步骤三:符合索引下推条件
- 步骤四:回表 找到对应的聚簇索引记录,给对应的聚簇索引记录加S型记录锁,也就是 number = 20
- 步骤五:符合边界条件
- 步骤六:server层判断country的条件不符合,释放掉加在二级索引记录上以及对应的聚簇索引记录上的锁
- 步骤七:获取孙权所在单链表的下一条记录,也就是荀彧的二级索引记录
对name值为'x荀彧'的二级索引记录进行分析
- 步骤二:加S型记录锁
- 步骤三:符合索引下推条件
- 步骤四:回表 找到对应的聚簇索引记录,给对应的聚簇索引记录加S型记录锁,也就是 number = 15
- 步骤五:符合边界条件
- 步骤六:server层判断country的条件符合,将其发送给客户端,并且不释放加在该记录上的锁
- 步骤七:获取所在单链表的下一条记录,也就是'z诸葛亮'的二级索引记录
对name值为'z诸葛亮'的二级索引记录进行分析
- 步骤二:加S型记录锁
- 步骤三:不符合索引下推条件,且不符合边界条件条件。不再去找下一条记录,直接向server层报告"查询完毕"信息
- 步骤四:跳过
- 步骤五:跳过
- 步骤六:server层收到存储引擎层报告的"查询完毕"信息,结束查询
需要注意的是:对于 孙权的二级索记录以及对应的number=20的聚簇索引记录,都是先加锁后释放锁。对应'z诸葛亮'的二级索引在步骤三被判断不符合边界条件,而且该步骤并不会释放加在该记录上的锁,而是直接向server层报告'查询完成'信息,因此导致整个语句在执行结束后也不会释放加在name值为'z诸葛亮'的二级索引记录上的锁。
在隔离级别不小于RR时的加锁过程
对name值为'l刘备'的二级索引记录进行分析
- 步骤二:这里与RC不同不同的是加的是S型next-key锁
- 步骤三:'l刘备'符合索引下推的条件
- 步骤四:因为是二级索引记录,所以需要对记录执行回表操作,找到相应的聚簇索引记录,也就是number为1的聚簇索引记录,然后为该聚簇索引记录加上一个S型记录锁----这里与RC加的锁相同
- 步骤五:符合边界条件
- 步骤六:server层判断'l刘备'的二级索引符合条件country!='吴',然后发送给客户端,并且不释放加在该记录上的锁
- 步骤七:获取刘备所在单链表的下一条记录,也就是孙权的二级索引记录
对name值为's孙权'的二级索引记录进行分析
- 步骤二:加S型next-key锁
- 步骤三:符合索引下推条件
- 步骤四:回表 找到对应的聚簇索引记录,给对应的聚簇索引记录加S型记录锁,也就是 number = 20
- 步骤五:符合边界条件
- 步骤六:server层判断country的条件不符合,但是由于现在的隔离级别不小于RR,所以不释放掉加在该记录上的锁
- 步骤七:获取孙权所在单链表的下一条记录,也就是荀彧的二级索引记录
对name值为'x荀彧'的二级索引记录进行分析
- 步骤二:加S型next-key锁
- 步骤三:符合索引下推条件
- 步骤四:回表 找到对应的聚簇索引记录,给对应的聚簇索引记录加S型记录锁,也就是 number = 15
- 步骤五:符合边界条件
- 步骤六:server层判断country的条件符合,将其发送给客户端,并且不释放加在该记录上的锁
- 步骤七:获取所在单链表的下一条记录,也就是'z诸葛亮'的二级索引记录
对name值为'z诸葛亮'的二级索引记录进行分析
- 步骤二:加S型next-key锁
- 步骤三:不符合索引下推条件,且不符合边界条件条件。不再去找下一条记录,直接向server层报告"查询完毕"信息
- 步骤四:跳过
- 步骤五:跳过
- 步骤六:server层收到存储引擎层报告的"查询完毕"信息,结束查询
在隔离级别不小于RR的情况下,该语句在执行过程中对'l刘备,S孙权,X荀彧,Z诸葛亮'的二级索引记录都加上S型next-key锁,对number值为1、15、20的聚簇索引记录加S型正经记录锁。
SELECT...FOR UPDATE语句的加锁过程
- SELECT...FOR UPDATE加锁过程与SELECT...LOCK IN SHARE MODE语句类型,只不过为记录加的是X锁。
Update语句加锁分析
Update语句加锁的方式与SELECT...FOR UPDATE类似,不同的是,如果更新了二级索引列,那么所有被更新的二级索引记录在被更新之前都需要加入X型正经记录锁。
UPDATE hero SET name = 'cao曹操' where number > 1 AND number <=15 AND country = '魏'
因为会更新name列,所有在更新前需要为idx_name二级索引中对应的记录加锁。
在不大于RC级别下该语句会给 'c曹操'和'x荀彧'的聚簇索引记录和二级索引记录加锁,对于number = 3的记录来说,由于它不符合country='魏'这个条件,所以先加锁后释放锁。对于number = 20的聚簇索引记录来说,由于不符合边界条件,所以也是先加锁后释放锁。
在不小于RR级别下,number列 number = 3、8、15、20列会被加上next-key锁,二级索引列name ='c曹操'、'x荀彧'会被加上X型的记录锁
加锁分析:
UPDATE hero SET name = 'cao曹操' where number > 1 AND number <=15 AND country = '魏'
- 首先定位到第一个不下于1的记录为number = 3,给该记录加上next-key锁
- 因为是聚簇索引不存在索引下推和回表
- server判断符合country条件不符合。
- 再找到number = 8,给该记录加上next-key锁
- server判断符合country条件符合所以准备更新列,然后去锁对应的二级索引。
- 再找到对应的number = 15 ,给该记录加上next-key锁
- server判断符合country条件符合锁对应的二级索引
- 再找到number = 20,给该记录加上next-key锁
- 由于不符合边界条件,所以直接结束。
Delete语句加锁分析
对于Delete语句,加锁的方式与SELECT...FOR UPDATE语句类似,只不过如果表中包含二级索引,那么二级索引记录在被删除之前都需要加上X型记录锁。