InnoDB存储引擎支持表锁和行锁。顾名思义,表锁是锁住整张表,行锁只是锁住某些行。InnoDB经过给索引项加锁来实现行锁,若是没有索引,则经过隐藏的聚簇索引来对记录加锁。若是操做不经过索引条件检索数据,InnoDB 则对表中的全部记录加锁,实际效果就和表锁同样。InnoDB存储引擎有3种行锁的算法,分别是:算法
以下图所示,数据库
例如一个索引有10,11,13,20这四个值。InnoDB能够根据须要使用Record Lock将10,11,13,20四个索引锁住,也可使用Gap Lock将(-∞,10),(10,11),(11,13),(13,20),(20, +∞)五个范围区间锁住。Next-Key Locking相似于上述两种锁的结合,它能够锁住的区间有为(-∞,10],(10,11],(11,13],(13,20],(20, +∞),能够看出它即锁定了一个范围,也会锁定记录自己。服务器
InnoDB存储引擎的锁算法的一些规则以下所示,后续章节会给出对应的实验案例和详细讲解。性能
在不经过索引条件查询时,InnoDB 会锁定表中的全部记录。你们能够登陆上本身的MySQL服务器,亲自试验一下。优化
试验发现,会话二的查询操做真的是会发生等待。那么,这句话真的是对的吗?注意必须在会话二操做还在等待时进行查询,不然查询不到。ui
其中locktrxid为1851的事务是会话二的事务,另外一个是会话一的事务。咱们能够看到两个锁都要对值为1的主键索引加锁。须要注意的是,这里是对主键进行加锁。两者之间的关系是怎么肯定的呢?咱们能够经过 information_schema.INNODB_LOCK_WAITS中的数据肯定。spa
奇怪,不是说好的锁定表中的全部记录嘛?查找了不少资料,发现 INNODB_LOCKS的定义以下:线程
The INNODB_LOCKS table contains information about each lock that an InnoDB transaction has requested but not yet acquired, and each lock that a transaction holds that is blocking another transaction.3d
也就是说,这张表并不会显示全部锁的信息,而是只显示要申请却没有申请到,和已经持有锁而且阻塞其余线程的锁信息。怪不得必须在会话二进行等待时进行查询才能查获得数据。orm
由于两个会话的操做都要锁住全部的行,因此发现每次在第一行记录上就发生了锁等待。那咱们使用插入语句试试。表e1的主键a的值为1-4,咱们分别插入主键为1-4(固然会有主键重复问题,可是因为有锁,一直等待)的新记录,分别查询锁信息,就能看到会话一的事务对全部的主键都加了锁,也就是对全部的记录都加了锁。
InnoDB存储引擎的行锁是经过锁住索引实现的,而不是记录。这是理解不少数据库锁问题的关键。
因为InnoDB特殊的索引机制,数据库操做使用主键索引时,InnoDB会锁住主键索引;使用非主键索引时,InnoDB会先锁住非主键索引,再锁定主键索引。不了解InnoDB索引机制的能够参考这篇文章
以下图所示,当InnoDB锁定非主键索引b时,它也会锁住其对应的主键索引,因此锁住b值为2和3的非主键索引,那么与其相关的a值为6,5的主键索引也须要被锁住。
好比说,一种常见的死锁状况通常出如今以下图所示的操做场景中。
会话一的语句使用了b上的索引,由于它是非主键索引,因此会先在b索引上添加锁,再去a索引上加锁。而会话二的语句偏偏相反,会先在索引a上加锁,再去索引b加锁。这种状况下,就可能出现死锁。
默认隔离级别REPEATABLE-READ下,InnoDB中行锁默认使用算法Next-Key Lock,只有当查询的索引是惟一索引或主键时,InnoDB会对Next-Key Lock进行优化,将其降级为Record Lock,即仅锁住索引自己,而不是范围。当查询的索引为辅助索引时,InnoDB则会使用Next-Key Lock进行加锁。InnoDB对于辅助索引有特殊的处理,不只会锁住辅助索引值所在的范围,还会将其下一键值加上Gap LOCK。
废话很少说,咱们来看一下相关的实验,先作一下准备。
CREATE TABLE e4 (a INT, b INT, PRIMARY KEY(a), KEY(b)); INSERT INTO e4 SELECT 1,1; INSERT INTO e4 SELECT 3,1; INSERT INTO e4 SELECT 5,3; INSERT INTO e4 SELECT 7,6; INSERT INTO e4 SELECT 10,8;
而后开启一个会话执行下面的语句。
SELECT * FROM e4 WHERE b=3 FOR UPDATE;
由于经过索引b来进行查询,因此InnoDB会使用Next-Key Lock进行加锁,而且索引b是非主键索引,因此还会对主键索引a进行加锁。对于主键索引a,仅仅对值为5的索引加上Record Lock(由于以前的规则)。而对于索引b,须要加上Next-Key Lock索引,锁定的范围是(1,3]。除此以外,还会对其下一个键值加上Gap Lock,即还有一个范围为(3,6)的锁。
你们能够再新开一个会话,执行下面的SQL语句,会发现都会被阻塞。
SELECT * FROM e4 WHERE a = 5 FOR UPDATE; # 主键a被锁 INSERT INTO e4 SELECT 4,2; # 插入行b的值为2,在锁定的(1,3]范围内 INSERT INTO e4 SELECT 6,5; # 插入行b的值为5,在锁定的(3,6)范围内
InnoDB引擎采用Next-Key Lock来解决幻读问题。由于Next-Key Lock是锁住一个范围,因此就不会产生幻读问题。可是须要注意的是,InnoDB只在Repeatable Read隔离级别下使用该机制。