【MySQL】锁

锁是计算机协调多个进程或线程并发访问某一资源的机制。在数据库中,除传统的计算资源(CPU、RAM、I/O)的争用以外,数据也是一种供许多用户共享的资源。如何保证数据并发访问的一致性、有效性是所有数据库必须解决的一个问题,锁冲突也是影响数据库并发访问性能的一个重要因素。从这个角度来说,锁对数据库而言显得尤其重要,也更加复杂。

MySQL中的锁,按照锁的粒度分,分为以下三类:

  • 全局锁:锁定数据库中的所有表。
  • 表级锁:每次操作锁住整张表。
  • 行级锁:每次操作锁住对应的行数据。

按照锁的类型来分,可以分为以下两类:

  • 读锁:又称共享锁或S锁,若事务T对数据对象A加上S锁,则事务T可以读A但不能修改A,其他事务只能再对A加S锁,而不能加X锁,直到T释放A上的S 锁。这保证了其他事务可以读A,但在T释放A上的S锁之前不能对A做任何修改。
  • 写锁:又称为排他锁或X锁。若事务T对数据对象A加上X锁,事务T可以读A也可以修改A,其他事务不能再对A加任何锁,直到T释放A上的锁。这保证了其他事务在T释放A上的锁之前不能再读取和修改A。

锁与锁之间的关系分为两类:

  • 兼容:如果锁A和锁B兼容,那么当一个数据对象上已经存在锁A时,我们可以再为其加上锁B,例如读锁与读锁之间就是兼容的
  • 互斥:如果锁A和锁B互斥,那么当一个数据对象上已经存在锁A时,我们再尝试为其加上锁B,当前事务就会被阻塞,例如读锁与写锁之间就是互斥的,写锁与写锁之间也是互斥的

上述概念大家先做了解,接下俩我们以锁的粒度分类为主线,详细介绍一下数据库中的锁。


1 全局锁

全局锁就是对整个数据库实例加锁,加锁后整个实例就处于只读状态,后续的DML的写语句,DDL语句,已经更新操作的事务提交语句都将被阻塞。需要注意的是,数据库实例指的是一个mysql进程而不是指某个具体的数据库。这两个概念略有不同,数据库是存储数据的仓库,而数据库实例是访问数据库的APP应用,如果我们对一个数据库实例加上了全局锁,那么我们就只能通过这个数据库实例对数据库进行读操作,不能再进行写操作。

全局锁典型的使用场景是做全库的逻辑备份,对所有的表进行锁定,从而获取一致性视图,保证数据的完整性。为什么做全库的逻辑备份,就需要为数据库实例加上全局锁呢?我们一起先来分析一下不加全局锁,可能存在的问题:

假设在数据库中存在这样三张表:tb_stock 库存表,tb_order 订单表,tb_orderlog 订单日志表,现在我们要对这三张表做数据备份,如果我们在做备份时没有加锁,那么可能就会出现如下情况:

  • 在进行数据备份时,先备份了tb_stock库存表。
  • 然后接下来,在业务系统中,执行了下单操作,扣减库存,生成订单(更新tb_stock表,插入tb_order表)。
  • 然后再执行备份 tb_order表的逻辑。
  • 业务中执行插入订单日志操作。
  • 最后,又备份了tb_orderlog表。

在这里插入图片描述

此时备份出来的数据是存在问题的。tb_stock表与tb_order表会出现数据不一致的情况(订单已经生成,但是库存并未扣减)

而一旦我们在执行备份之前为数据库加上了全局锁,那么当我们在进行逻辑备份时,其他的DDL、DML全部都会处于阻塞状态,只有DQL语句能够被执行,这也就是我们常说的只读状态,由于数据备份操作本身就是查询操作,因此不会收到任何影响,这样我们就能保证了备份过程中数据库的数据是不会发生变化的,保证了数据的一致性和完整性。

上述操作过程中会用到的指令如下:

加全局锁

flush tables with read lock ;

数据备份

-- 将指定数据库备份到指定路径
-- 注意数据备份的指令是命令行指令而不是mysql指令,因此当我们加上全局锁后,需要打开另一个命令行窗口来备份数据

mysqldump -uroot –p123456 itcast > D:/mysqldump/itcast.sql

释放锁

unlock tables ;

加上全局锁虽然能解决数据备份中存在的数据不一致问题,但是在数据库中加全局锁,是一个比较重的操作,它会存在以下问题:

  • 如果在主库上备份,那么在备份期间都不能执行更新,业务基本上就得停摆。

  • 如果在从库上备份,那么在备份期间从库不能执行主库同步过来的二进制日志(binlog),会导致主从延迟。

在InnoDB引擎中,我们可以在备份时加上参数 --single-transaction参数来完成不加锁的一致性数据备份,这个指令会在执行时对当前数据库生成一份快照,并对快照进行备份

mysqldump --single-transaction -uroot –p123456 itcast > D:/mysqldump/itcast.sql

2 表级锁

表级锁,每次操作锁住整张表。锁定粒度大,发生锁冲突的概率最高,并发度最低。应用在MyISAM、InnoDB、BDB等存储引擎中。对于表级锁,主要分为表锁、元数据锁(meta data lock,MDL)、意向锁。

2.1 表锁

表锁分为两类,分别是表共享读锁(read lock)和表独占写锁(write lock),其主要语法如下:

加锁

lock tables 表名... read | write

释放锁

unlock tables / 客户端断开连接
2.1.1 表共享读锁

表共享读锁是一种读锁(共享锁),当客户端A为某张表加上了表共享读锁时,那么客户端A就只能对该表进行读操作,如果进行写操作会报错;其他客户端也只能对该表进行读操作,如果进行了写操作则会被阻塞,直到客户端A释放锁,写操作才能被执行。

在这里插入图片描述

我们可以来测试一下,现在itheima数据库中,有一张stu表,我们可以打开一个客户端连接(后文简称客户端A),并尝试为其加上读锁,然后进行读写操作:

在这里插入图片描述

此时锁并没有被释放,我们可以尝试打开另一个客户端连接(后文简称客户端B),并通过以下指令查看当前数据库的锁情况:

select object_type,object_schema,object_name,lock_type,lock_duration from performance_schema.metadata_locks where object_schema = 'itheima';

在这里插入图片描述

我们需要注意的是图中红色框里的信息,它表示的是在itheima数据库的stu表中有一把共享只读锁(shared_read_only)

此时客户端A并未释放锁,我们可以尝试在客户端B对stu表进行读写操作:

在这里插入图片描述

可以看到,读操作是可以正常执行的,但是写操作却会被阻塞,这时我们再将客户端A中的表共享读锁释放:

在这里插入图片描述

再查看客户端B,发现当锁被释放之后,更新语句就被执行了,

在这里插入图片描述

2.1.2 表独占写锁

表独占写锁是一种写锁(排他锁),当客户端A为某张表加上了表独占写锁时,那么客户端A可以对该表进行读写操作;其他客户端对该表的读写操作都会被阻塞,直到客户端A释放锁。

在这里插入图片描述

同样的,在客户端A中对stu加上写锁,然后进行读写操作:

在这里插入图片描述

这里我们发现无论是读写操作都能正常执行,此时锁还没有被释放,我们可以在客户端B中通过以下指令查看当前数据库中的锁情况:

select object_type,object_schema,object_name,lock_type,lock_duration from performance_schema.metadata_locks where object_schema = 'itheima';

在这里插入图片描述

这里我们会发现有两把锁,一把是意向排他锁(intention exclusive),这个我们将在后面进行介绍。另一把自然就是表独占写锁(shared_no_read_write)。

在客户端B中,我们尝试进行读操作:

在这里插入图片描述

发现被阻塞了,这是因为客户端A还未释放锁,此时进行写操作也一样会被阻塞,这里不再单独进行演示

当我们将客户端A的写锁释放后,客户端B的读操作就会被正常执行。

2.2 元数据锁

meta data lock , 元数据锁,简写MDL。

MDL加锁过程是系统自动控制,无需我们手动通过指令加锁或释放锁,当我们在访问一张表的时候系统会自动为我们加上MDL锁。MDL锁主要作用是维护表元数据的数据一致性,在表上有事务正在进行时的时候,不可以对元数据进行写入操作。为了避免DML与DDL冲突,保证读写的正确性。

这里的元数据,大家可以简单理解为就是一张表的表结构。 也就是说,某一张表上有未提交的事务时,我们是不能对这张表的结构进行修改的。

在MySQL5.5中引入了MDL,当对一张表进行增删改查的时候,加MDL读锁;当对表结构进行变更操作的时候,加MDL写锁。常见的SQL操作时,所添加的元数据锁如下:

对应SQL锁类型说明
lock tables xxx read /writeSHARED_READ_ONLY /SHARED_NO_READ_WRITE
select 、select …lock in share modeSHARED_READ与SHARED_READ、SHARED_WRITE兼容,与EXCLUSIVE互斥
insert 、update、delete、select … for updateSHARED_WRITE与SHARED_READ、SHARED_WRITE兼容,与EXCLUSIVE互斥
alter table …EXCLUSIVE与其他的MDL都互斥

当我们再执行增删改查操作时,mysql会为表加上共享锁(SHARED),无论添加的SHARED_READ还是SHARED_WRITE,它们之间都是兼容的,即一个事务在对某张表进行读写操作时,其他事务也可以对这张表进行写读写操作,但是当我们执行表结构修改语句时,mysql会为表加上排他锁(EXCLUSIVE)。而排他锁与共享锁之间是不兼容的,也就是说当一个事务在对某张表进行读写操作时,其他事务是不可以对这张表的表结构进行修改的。需要额外注意的是,EXCLUSIVE与表锁(SHARED_READ_ONLY /SHARED_NO_READ_WRITE)之间也是互斥的,也就是当一个事务为一张表添加上表锁后,其他事务也不能修改该表的表结构。

这里我们可以来进行演示,同样使用itheima数据库的stu表,当然这次演示我们需要手动开启一个事务,因为元数据锁只会针对事务操作进行加锁。

在客户端A中,手动开启一个事务,并进行一次读操作。

在这里插入图片描述

按照上面的说法,此时系统应该会自动为stu表加上一个SHARED_READ,我们可以打开客户端B,并通过以下指令验证一下我们的猜想:

select object_type,object_schema,object_name,lock_type,lock_duration from performance_schema.metadata_locks where object_schema = 'itheima';

在这里插入图片描述

此时由于客户端A的事务仍未提交,我们可以在客户端B中尝试修改一下表结构:

在这里插入图片描述

我们发现修改表结构的sql语句是会被阻塞的,此时我们可以将客户端A中的事务进行提交

在这里插入图片描述

再次查看客户端B,发现表结构修改语句已经执行完毕,我们可以再次查询一下stu表,发现字段确实已经被添加了

在这里插入图片描述

2.3 意向锁

我们先思考一下,假如在客户端A中针对stu表的某一行数据加了一个行锁(行锁具体后面会讲到,其实就是针对表中某一行数据加锁),然后客户端B又试图为stu表添加一个表锁,那么添加过程是怎么样的呢?我们可以通过示意图来简单分析一下:

首先客户端A开启一个事务,然后执行DML操作,并对涉及到的行加上一个行锁。

在这里插入图片描述

当客户端B,想对这张表加表锁时,会检查当前表是否有对应的行锁,那么为什么其他客户端添加表锁需要先检查表中是否有行锁呢?之前我们提到过,锁与锁之间是存在一种互斥关系的,如果已被添加的行锁为排他锁,那么我们是不能为这张表添加任何锁的(因为排他锁与所有锁都是互斥的)

上述检查行锁的过程效率是比较低的,因为会从第一行数据检查到最后一行数据。

在这里插入图片描述

针对上述过程效率底下的问题,InnoDB使用了意向锁来解决。有了意向锁之后:

客户端A在执行DML操作时,会对涉及的行加行锁,同时也会对该表加上意向锁:

在这里插入图片描述

而其他客户端,在对这张表加表锁的时候,会根据该表上所加的意向锁来判定是否可以成功加表锁,而不用逐行判断行锁情况了。

在这里插入图片描述

意向锁分为以下两类:

  • 意向共享锁(IS):由语句select … lock in share mode添加。与表锁共享锁(read)兼容,与表锁排他锁(write)互斥。
  • 意向排他锁(IX):由insert、update、delete、select…for update添加。与表锁共享锁(read)及排他锁(write)都互斥,意向锁之间不会互斥。

由上见,意向锁更多是一个标志作用,意向锁之间不会排斥就已经说明了这点。当我们添加的行锁类型为共享锁时,意向锁的类型就为共享锁,当我们添加的行锁类型为排他锁时,意向锁的类型就为排他锁。关于行锁的具体内容会在下文介绍,这里大家先留一个印象。

需要注意的是,一旦事务提交了,意向共享锁、意向排他锁,都会自动释放。

这里我们稍微演示一下:

我们可以在客户端A中开启一个事务,并执行一个select … lock in share mode语句(这个语句的详细作用将在行锁部分进行讲解,这里暂时就理解为执行select语句的时候为涉及到的行加上行锁就可以了)

在这里插入图片描述

此时我们可以打开客户端B,并通过以下指令检查当前数据库中意向锁和行锁的情况(注意:这个指令与我们之前用的检查锁的指令略有不同,这个指令只能检查当前表中意向锁和行锁的情况,而无法检查表锁和元数据锁的情况)

select object_schema,object_name,index_name,lock_type,lock_mode,lock_data from performance_schema.data_locks;

在这里插入图片描述

此时我们可以看到,此时stu数据库上有两把锁,第一把锁是IS,也就是意向共享锁,而第二把锁是SREC_NOT_GAP,也就是我们之前提到过的行锁,当我们通过sql语句为某张表添加上行锁时,innodb引擎就会自动为这张表添加上对应类型的意向锁。

此时我们可以尝试在客户端B中分别尝试为表stu添加表共享读锁或表独占写锁:

在这里插入图片描述

当我们添加表共享读锁时,是能成功添加的,因为此时意向锁的类型是共享锁,但是当我们添加表独占写锁时,就会被阻塞,那么阻塞何时会结束呢?很显然是客户端A中的事务提交的时候:

在这里插入图片描述

此时再查看客户端B,发现表独占写锁已经上锁成功了,此时我们再查看当前数据库中意向锁和行锁的情况,发现两把锁都已经被释放了。这是因为客户端A中涉及到行锁的事务已经被提交了。

在这里插入图片描述

如果我们在客户端A的事务中执行的是insert、update、delete、select…for update等等语句,那么客户端B无论是添加表共享读锁还是表独占写锁都会被阻塞,因为上述sql语句添加的行锁类型为排他锁,因此innodb引擎为stu表添加的意向锁类型也为排他锁,排他锁与共享锁(表共享写锁就是共享锁)、排他锁(表独占写锁就是排他锁)之间都是互斥的。限于篇幅原因,这里不再单独演示,小伙伴可以自行尝试一下。


3 行级锁

行级锁,每次操作锁住对应的行数据。锁定粒度最小,发生锁冲突的概率最低,并发度最高。应用在InnoDB存储引擎中。

InnoDB的数据是基于索引组织的,行锁是通过对索引上的索引项加锁来实现的,因此行锁是对索引加的锁,而不是对记录加的锁。对于行级锁,主要分为以下三类:

  • 行锁(Record Lock):锁定单个行记录的锁,防止其他事务对此行进行update和delete。在RC、RR隔离级别下都支持。

在这里插入图片描述

  • 间隙锁(Gap Lock):锁定索引记录间隙(不含该记录),确保索引记录间隙不变,防止其他事务在这个间隙进行insert,产生幻读。在RR隔离级别下都支持。

在这里插入图片描述

  • 临键锁(Next-Key Lock):行锁和间隙锁组合,同时锁住数据,并锁住数据前面的间隙Gap。 在RR隔离级别下支持。

在这里插入图片描述

3.1 行锁

跟表锁一样,行锁也分为以下两种类型:

共享锁(S):允许一个事务去读一行,阻止其他事务获得相同数据集的排它锁,但是允许其他事务获得相同数据集的共享锁

排他锁(X):允许获取排他锁的事务更新数据,阻止其他事务获得相同数据集的共享锁和排他锁。

常见的SQL语句,在执行时,所加的行锁如下:

SQL行锁类型说明
INSERT …排他锁自动加锁
UPDATE …排他锁自动加锁
DELETE …排他锁自动加锁
SELECT(正常)不加任何锁
SELECT … LOCK IN SHARE MODE共享锁需要手动在SELECT之后加LOCK IN SHARE MODE
SELECT … FOR UPDATE排他锁需要手动在SELECT之后加FOR UPDATE

我们需要注意的是SELECT ... LOCK IN SHARE MODESELECT ... FOR UPDATE ,这两种sql其实就和普通的select语句功能是一样的,引进这两种形式的原因是因为普通的select语句是不加锁的,但是我们在有些业务场景需要针对select语句加锁,因此就可以通过SELECT … LOCK IN SHARE MODE为普通的select语句加上共享锁,或者通过SELECT … FOR UPDATE 为普通的sql语句加上排他锁。

接下来我们对行锁进行简单的演示:

打开客户端A,开启一次事务并执行一次普通的select语句,执行时并不会加锁。

在这里插入图片描述

当我们执行select…lock in share mode,会加上共享锁,此时共享锁锁住的数据时id为1的那一行数据。与共享锁一起被加上的当然还有我们之前介绍的意向锁。

在这里插入图片描述

共享锁与共享锁之间是兼容的,此时我们可以打开客户端B,再次为id为1的数据添加一把共享锁,并通过以下sql语句查看当前数据库中意向锁和行锁的情况:

select object_schema,object_name,index_name,lock_type,lock_mode,lock_data from performance_schema.data_locks;

在这里插入图片描述

我们发现此时数据中是有四把锁的,很显然,其中的两把是客户端A在执行事务的时候添加的共享锁和意向锁,而另外两把是客户端B在执行事务的时候添加的共享锁和意向锁,共享锁与共享锁之间是兼容的,意向锁与意向锁之间是兼容的,而这里的意向锁又是意向共享锁,与共享锁之间也是兼容的,因此这四把锁可以同时存在

仍然是在客户端B,我们尝试使用update语句为id为1的数据添加排他锁,结果很显然是会被阻塞的:

在这里插入图片描述

当我们提交客户端A中的事务之后,客户端B的更新语句自然才会执行成功(这里偷懒,就不配图了)

需要注意的是,行锁锁住的只是某一行数据,例如在上述案例中锁住的就只是id为1的那一行数据,其他行的数据客户端B还是可以正常读写的。

回归客户端A,将上一次事务提交后,这次我们再开启一个事务,并尝试通过update语句为id为1的数据加上一个排他锁,并通过指令查看当前数据库中锁的情况

在这里插入图片描述

这时我们发现添加的意向锁是意向排他锁,这是因为我们添加的行锁的类型是排他锁。

排他锁与共享锁之间是互斥的,排他锁与排他锁之间也是互斥的,所以现在我们在客户端B中针对id为1的数据,无论添加的是共享锁还是排他锁都会被阻塞:

在这里插入图片描述

这里有一点需要注意,前面我们也提到过,innodb引擎是针对索引加的行锁而非针对记录添加行锁,因此当我们以无索引数据为条件进行update时,innodb引擎并不会为被操作的行添加行锁,而是直接对整张表添加表独占写锁,这也就是常说的锁升级问题。具体解决方案很简单,就是为当前表建立额外索引,而且建立索引的字段需要包含update的条件字段。

3.2 间隙锁&临键锁

在默认情况下,InnoDB在 REPEATABLE READ事务隔离级别运行,InnoDB使用的是临键锁进行搜索和索引扫描而并非使用行锁,这样做的目的是防止幻读,但是在特定情况下,临键锁会变化成行锁或间隙锁:

当我们针对唯一索引对已存在的记录进行等值匹配的时候,临键锁会自动优化成行锁

例如我们针对主键索引进行查询:

在这里插入图片描述

这里其实和我们上文讲的行锁的知识是一致的,但是我们需要注意的是这里的行锁是临键锁退化而来的,也就是说如果这里的查询条件为非唯一索引的话,那么实际上这里加的行级锁就会是临键锁而非行锁。

索引上的等值查询(唯一索引),给不存在的记录加锁时, 优化为间隙锁

打开客户端A,进行如下操作:

在这里插入图片描述

如上图,id=5是一个根本不存在的数据,我们对其进行了更新操作,按照上面的理论,此时对id为5的数据的行锁会优化成间隙锁,我们可以打开客户端B,输入如下指令来验证我们的猜想:

select object_schema,object_name,index_name,lock_type,lock_mode,lock_data from performance_schema.data_locks;

在这里插入图片描述

此时stu上有两把锁,第一把是意向锁,我们先不管,第二把锁就是间隙锁(GAP表示当前锁是间隙锁,X表示当前锁是写锁)。我们注意到间隙锁的lock_data的值为8,这个表示间隙锁锁住的范围是id为8的数据之前的间隙,在这里也就是(3,8),注意这里是开区间。那么为什么会是8呢?因为当我们更新id为5的数据时,由于该数据不存在,因此innodb会向右遍历,并找到的第一个值建立间隙锁。

也就是说,此时id为3到id为8之间此时是不允许再插入数据的,我们可以尝试再客户端B中插入一条id为5的数据

在这里插入图片描述

结果不出所料,就是被阻塞了,只有当客户端A的事务被提交时,才能插入成功。

间隙锁唯一目的是防止其他事务插入间隙。间隙锁可以共存,一个事务采用的间隙锁不会阻止另一个事务在同一间隙上采用间隙锁。

索引上的等值查询(非唯一普通索引),向右遍历时最后一个值不满足查询需求时, 临键锁退化为间隙锁。

上述概念有点不太好理解,我们先来做一下测试:

首先我们需要针对age字段建立索引,并查询age为3的数据

在这里插入图片描述

当我们对age等于3的数据进行查询时,数据库会上四把锁:

  • 第一把锁是意向锁不做多解释。
  • 第二把锁和第三把锁加在一起组成临键锁,它锁住的是age为3以及3之前的间隙,那么为什么需要两把锁组合在一起呢?其实这里名为S的锁才是真正的临键锁,它的lock_data有两个值,第一个3表示age为3,第二个3表示id为3,但是这里的S锁住的仅仅是age索引中id为3且age为3的值,而age索引是一个二级索引,它保存的数据只有id和age,其他事务是可以通过主键索引修改该行数据的其他字段值的,因此第三把锁,也就是我们熟悉的行锁,它负责锁住主键索引中id为3的数据。
  • 第四把锁是间隙锁,它锁住的是age为8之前的数据,它的lock_data同样有两个值,第一个值表示age为8,第二个值表示id为8

那么为什么锁的分布会是上面那种情况呢?

我们针对了age建立了一个索引,那么该索引的B+树的叶子节点应该按照如下顺序排列(这里没有画图工具,大家将就着看哈):

id138111925
age138111925

当我们针对一个age字段进行查询时,即使找到了age为3的数据,innodb也不会停止查找,因为age是一个非唯一索引,因此该表中可能存在不止一个age为3的数据,但是当找到了第一个age为3的数据后,innodb会先对这个age为3的数据加上临键锁,锁住该数据以及该数据与前一个数据之间的空隙,加锁后,innodb引擎会继续往后查找,首先找到一个不满足查询条件的值,也就是8,此时innodb会为8加上间隙锁,即不锁住该行数据,只锁住该行数据之前的数据。

对上述过程进行总结,innodb会对age为3的数据加上临键锁,为age为8的数据加上间隙锁,综合来看,锁住的范围就是(1,8),注意,这里的1和8都是开区间。

那么为什么说索引上的等值查询(非唯一普通索引),向右遍历时最后一个值不满足查询需求时, 临键锁退化为间隙锁呢?因为这里我们只有一个age为3的数据,如果此时数据表中还有另外一个age为3的数据,由于索引底层是会对age字段进行排序的,因此当innodb找到了第一个age为3的数据并为其添加了临键锁之后,如果找到了第二个age为3的数据,innodb为其添加的锁仍然是临键锁,以此类推,直到innodb找到了第一个不为3的数据也就是8,由于age=8不满足查询条件,因此innodb无法为其添加临键锁,才会退而求其次对其添加间隙锁。这个间隙锁在这里的意义主要是为了防止在该事务未提交期间,其他事务再插入一条age为3的数据,导致出现不可重复读(在一次事务中两次查询得到的结果不一致)和幻读的问题

索引上的范围查询(唯一索引),会访问到不满足条件的第一个值为止。

我们可以尝试在客户端A进行一次范围查询:

在这里插入图片描述

打开客户端B,查看锁情况:

在这里插入图片描述

这里同样出现了四把锁,我们来一一分析一下每把锁的意义是什么:

  • 第一把锁是意向锁,这里同样不多做讨论
  • 第二把锁是行锁,由于这里我们的查询条件是age>=19,而id=19的数据正好满足age=19,又由于id是唯一索引,因此仅在这行数据上,临键锁优化成了行锁。
  • 第三把锁是临键锁,由于我们这次使用的是主键索引,因此只需要一把锁就足够了,这时可能有小伙伴会问,只锁住主键索引的话,如果有其他事务通过age索引来修改该行数据了怎么办呢?这个大可放心,因为innodb引擎下,数据都是组织在主键索引中的,只要是更新操作,最终都一定会走主键索引的,只要我们锁住了主键索引,更新操作就无法完成。第三把锁中lock_date的值为supremun pseudo-record,这个代表该临键锁是正无穷的临键锁,锁住了正无穷以及其之前的间隙。
  • 第四把锁是25的临键锁

为什么锁会出现如上分布情况呢?我们其实可以通过最终的查询结果来观察一下:

在这里插入图片描述

最终的查询结果有两条数据,一条是id为19的数据,一条是id为25的数据,经过之前的分析我们知道,innodb引擎在执行查询时会为符合结果的每个数据都加上一把临键锁,这里符合数据的结果有两个,19由于符合等值优化成了行锁(第二把锁),因此只有25被加上了临键锁(第四把锁),当然为25加上临键锁之后innodb还会继续往后查找有没有符合条件的数据,但是显然是没有了,因为25已经是最大的id值了,那么innodb引擎会就此罢休吗?当然不会,要知道临键锁本身就是用来防止幻读的,如果只锁住[19,25],那么如果有事务再插入了一条id为26的数据,那么仍然会出现幻读现象,因此innodb一不做二不休,直接将(25,+∞]之间的间隙也加上临键锁(第三把锁),临键锁的对象是无穷大,意思是哪怕你id值为无穷大,此刻也插入不进来。

  • 5
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值