本文转载自微信公众号《飞天小牛》,作者飞天小牛。转载本文请联系飞天小牛公众号。上面说了,对于InnoDB来说,可以随时加锁(加锁的SQL语句这里就不说了,忘记的朋友可以看前面的文章),但是不能随时加锁.具体来说,InnoDB采用了两阶段锁定协议:即在事务执行过程中,可以随时进行加锁操作,但只有在事务执行COMMIT或ROLLBACK时才会释放锁。并且同时释放所有的锁。而且,行级锁只在存储引擎层实现。对于InnoDB存储引擎来说,行级锁分为三种,或者说三种行级锁算法:RecordLock:记录锁GapLock:间隙锁Next-KeyLock:在键锁旁边,我们来解释一下这三种行详细锁定算法。记录锁顾名思义,记录锁就是锁定一行记录。实际上,它阻塞了该行的索引记录。如果创建的表没有任何索引,那么InnoDB存储引擎将使用“隐式主键”进行锁定。所谓隐式主键是指:如果在建表时没有指定主键,InnoDB存储引擎将使用第一个非空列作为主键;如果没有,它会自动生成一个6字节的主键。那么,既然RecordLock是基于索引的,如果我们的SQL语句中的条件导致索引失败(比如usingor)或者条件根本不涉及索引或者主键,行级锁将退化为表锁。RecordLock示例首先我们举一个查询索引字段的例子。数据库如下,id为主键索引:CREATETABLE`test`(`id`int(11)NOTNULLAUTO_INCREMENT,`username`varchar(255)DEFAULTNULL,PRIMARYKEY(`id`))ENGINE=InnoDBAUTO_INCREMENT=6DEFAULTCHARSET=utf8;初始数据如下:新建两个事务,首先执行事务T1的前两行,即不执行commit:因为commit没有执行,所以此时事务T1没有释放Lock,并锁定记录id=1的行,然后执行事务2申请id=2的记录行:可以看到,由于对不同的记录行加锁,所以两个记录锁是不互斥的。现在让我们看一下表中的数据。由于事务1还没有提交,应该只是修改了id=2的用户名:nice,果然。然后执行事务1的commit,id=1的username会被修改。行锁退化为表锁的例子再看不使用索引的例子:同样新建两个事务,先执行事务T1的前两行,即不执行commit。我们尝试使用select...forupdate给username="user_three"的记录行加一个记录锁,但是由于username不是主键也不是索引,事务T1实际上锁住了整张表:因为没有commit被执行了,所以此时事务T1并没有释放锁,而是锁住了整张表。这时候再次执行事务2,尝试申请id=5的记录锁,会发现事务T2会卡住,超时后最后关闭事务:两条不同的记录有相同的索引,会不会是锁冲突?这个问题的答案应该很简单,正如我们上面强调的,行锁锁定的是索引,而不是记录(只是我们平时说的是锁定哪条记录,这样比较容易理解)。所以如果两个事务操作的两个不同的记录有相同的索引,一个事务就会等待,因为行锁被另一个事务占用了。这里简单提一下GapLock,下面我会详细解释:与基于唯一索引的RecordLock不同,GapLock和Next-KeyLock都是基于非唯一索引。并且与RecordLock锁定某条索引记录不同,GapLock和Next-KeyLock将索引记录锁定在一个范围内:s??elect*fromtestwhereidbetween1and10forupdate;对于上面的SQL语句,所有在区间(1,10)(从左到右)的记录行都会被GapLock锁定,插入所有id为2,3,4,5,6的数据行,7、8和9将被阻止,但1和10将被阻止。操作的索引记录未锁定。笔记!这里指的是锁定(1,10)区间内的所有id,也就是说,即使一个id当前不在我们的表中,比如id=6,如果要插入一条id=6的新记录,对不起,没有。Next-KeyLockNext-KeyLock是一种结合了GapLock和RecordLock的锁定算法。它的主要目的是解决幻读问题。比如一个索引有10、11、13、20四个值,分别对四个索引进行锁操作,那么这四个操作对应的Next-KeyLock锁定的区间为:(-∞,10](10,11](11,13](13,20](20,+∞]细心的同学应该注意到了,和GapLock的区别在于next-keylock锁的是leftopen-right-closed,也就是说包含了当前正在操作的索引记录,在InnoDB默认的隔离级别REPEATABLE-READ下,行锁默认使用的算法是Next-KeyLock,但是如果操作的索引是唯一索引或者primarykey,InnoDB会优化Next-KeyLock,降级为RecordLock,即只锁索引本身,不锁范围。由于主键也是唯一索引,我们可以说:RecordLock是基于uniqueIndexed,而Next-KeyLock基于非唯一索引。需要注意的是,当操作的索引为非唯一索引时,InnoDB会先使用RecordLock锁定对应的唯一索引,然后使用Next-KeyLock和GapLock来处理非唯一索引,而不是只是锁定这个非唯一索引。具体来说,我们来看一个例子。Next-KeyLock示例假设我们在上面的测试表中添加一个新字段并将其设置为非唯一索引:CREATETABLE`test`(`id`int(11)NOTNULLAUTO_INCREMENT,`username`varchar(255)DEFAULTNULL,`class`int(11)NOTNULL,PRIMARYKEY(`id`),KEY`index_class`(`class`)USINGBTREECOMMENT'非唯一索引')ENGINE=InnoDBAUTO_INCREMENT=6DEFAULTCHARSET=utf8;insertsomedata:startatransaction1执行如下操作语句:select*fromtestwhereclass=3forupdate;在这种情况下,InnoDB实际上会加三种行锁(select*...fromupdate加行级写锁,即X锁):1)给主键索引id=105加上RecordLock2)对于非-uniqueindexclass=3,添加Next-KeyLock,锁定范围为(1,3]3)另外需要注意的是,InnoDB存储引擎还会将uniqueindexclass的nextkey值加上Gap锁(表中class=3的下一个键值为6),所以也存在class索引范围为(3,6)的间隙锁。总结以下2)和3),对于这条SQL语句,InnoDB存储引擎锁定的类索引范围是(1,6)。接下来,我们用实践来验证理论,然后开启一个事务2,执行如下语句:不出所料,因为在事务1中执行的SQL语句对主键中a=105列的记录加了X锁index,所以这里获取这条记录的X锁会被阻塞。然后用事务执行如下SQL语句:主键insert104没有问题,但是插入的类索引值2在锁定范围(1,6)内,所以也会阻塞执行。经过上面的分析,你一定能够知道下面的SQL语句是可以正常执行的:注意需要注意的是,Next-KeyLock降级为RecordLock只存在于操作所有唯一索引列的情况下。如果唯一索引由多个列组成,并且只操作多个唯一索引列中的一个,InnoDB存储引擎仍然使用Next-KeyLock进行锁定。
