MySQL是目前世界上最流行的数据库。InnoDB是MySQL最流行的存储引擎。在大数据量、高并发的业务场景中有非常好的表现。性能,之所以会这样,跟InnoDB的锁机制有关。总的来说,InnoDB有七种锁:(1)Auto-incLocks;(2)共享锁和排他锁;(3)意向锁;(4)插入意向锁(InsertIntentionLocks);(5)记录锁(RecordLocks);(6)缝隙锁(GapLocks);(7)Next-keyLocks(下一键锁);今天就和大家一起来一一介绍。文章较长,案例较多。请收藏、点赞、转发,提前阅读。第一个,Auto-incLocks【案例描述】MySQL、InnoDB,默认隔离级别(RR),假设有一个数据表:t(idAUTO_INCREMENT,name);数据表中有数据:shenjianzhangsanlisi交易A先执行,尚未提交:insertintot(name)values(xxx);事务B执行后:insertintot(name)values(ooo);Q:交易B会不会被阻塞?【案例分析】InnoDB隔离在RR下层,尝试解决幻读问题。上述情况:(1)事务A先执行insert,会得到一条(4,xxx)记录。由于是自增列,不需要显示指定id为4,InnoDB会自动增长,注意此时事务还没有提交;(2)在事务B之后执行insert,假设不会阻塞,会得到一条(5,ooo)记录;这时候没有错,但是如果,(3)事务A继续插入:insertintot(name)values(xxoo);将得到(6,xxoo)的记录。(4)事务A再次select:select*fromtwhereid>3;得到的结果是:4,xxx6,xxoo画外音:无法查询到5的记录,在RR的隔离级别下,无法读取未提交的事务生成数据。这对于事务A来说很奇怪,在AUTO_INCREMENT的列中,连续插入了两条记录,一条是4,下一条就变成了6,就像一个莫名其妙的幻影。【自增锁】自增锁是一种特殊的表级锁(table-levellock),专门插入到AUTO_INCREMENT类型的列中,供事务使用。在最简单的情况下,如果一个事务正在向表中插入记录,则所有其他事务插入都必须等待,以便第一个事务插入的行是连续的主键值。画外音:官网是这么说的AUTO-INC锁是一种特殊的表级锁,由事务插入到具有AUTO_INCREMENT列的表中获取。在最简单的情况下,如果一个事务正在向表中插入值,则任何其他事务必须等待自己向该表中执行插入,以便第一个事务插入的行接收连续的主键值。同时,InnoDB提供了innodb_autoinc_lock_mode配置,可以调整和改变锁的模式和行为。【如果不是自增列】上面的例子,如果不是自增列,会发生什么?t(iduniquePK,名称);数据表中有数据:10,shenjian20,zhangsan30,lisi先执行事务A,在10和20的两条记录中插入一行,还没有提交:insertintotvalues(11,xxx);事务B执行后,在10和20的两条记录中也插入了一行:insertintotvalues(12,ooo);到这里,那就不再使用自增锁了,那么:会用到什么锁呢?事务B会被阻塞吗?先说个道理,以后再回答。第二种,共享锁和排他锁(SharedandExclusiveLocks)《InnoDB并发如此高,原因竟然在这?》本文介绍的是通用的共享/排他锁。当然,InnoDB中也实现了标准的行级锁定。Shared/ExclusiveLocks独占锁:一个事务只有获得了一条记录的共享S锁才能读取一行;一个事务只有在获得了一条行记录的排他X锁时,才能修改或删除一行;其兼容互斥表如下::多个事务可以获得一个S锁,读取和读取可以并行;而只有一个事务可以获得X锁,写/写/读和写必须是互斥的;共享锁/排他锁的潜在问题是不能完全并行化,解决办法是多版本数据。具体思路在《InnoDB并发如此高,原因竟然在这?》中已经介绍过,这里不再赘述。第三种,意向锁(IntentionLocks)InnoDB支持多粒度锁,允许行级锁和表级锁并存。在实际应用中,InnoDB使用意向锁。意向锁是指在未来某个时刻,一个事务可能需要加共享/排他锁,所以要提前申明一个意向。意向锁具有以下特点:(1)第一,意向锁是表级锁;(2)意向锁分为:意向共享锁(intentionsharedlock,IS),表示,事务打算对表的某些行加共享S锁。意向排他锁(intentionexclusivelocks,IX),表示事务有意对表中的某些行加排他X锁。例如:select...lockinsharemode,需要设置ISlock;选择...进行更新,需要设置IX锁;(3)意向锁协议(intentionlockingprotocol)并不复杂:事务需要获取某些行的S锁,必须先获取表的IS锁事务。要获取某些行的X锁,必须先获取该表的IX锁(4)由于意向锁只是表示意向,所以其实是一个比较弱的锁,意向锁之间是不互斥的。互斥,但可以并行,其兼容互斥表如下:(5)Amount,既然意向锁是相互兼容的,意义何在?会与共享锁/排他锁互斥,其兼容性互斥表如下:画外音:排他锁是强锁,不兼容其他类型的锁。也很容易理解,当修改或删除某行时,必须获取强锁,禁止其他并发对该行,保证数据的一致性。第四种,插入意向锁(InsertIntentionLocks)修改和删除已有数据行,必须加强互斥X锁,那么对于数据的插入,是否需要加这么强的锁来实现互斥??插入意向锁,孕育而生。插入意向锁是一种间隙锁(GapLocks)(所以,它也是在索引上实现的),专门针对插入操作。画外音:有点尴尬。间隙锁将在后面介绍。暂时理解为在索引上实现的锁,锁定索引的一定范围。它的玩法是:多个事务在同一个索引同一个范围内插入记录时,如果插入的位置不冲突,则不会互相阻塞。画外音:根据官网介绍,InsertIntentionLock表示插入意图,多个交易插入同一个索引缺口,如果不是在缺口内的同一位置插入,则无需相互等待。坑的例子可以回答。MySQL、InnoDB、RR下:t(iduniquePK,name);数据表中有数据:10,shenjian20,zhangsan30,先执行lisi事务A,在10和20两条记录中插入一行,还没有提交:insertintotvalues(11,xxx);在事务B之后执行,在10和20两条记录中也插入了一行:insertintotvalues(12,ooo);会用到什么锁?事务B会被阻塞吗?答:虽然事务隔离级别为RR,虽然是同一个索引,虽然是同一个区间,但是插入的记录并不冲突,所以这里:使用了插入意向锁;事务B不会被阻塞;【思路总结】InnoDB使用共享锁,可以提高读写的并发性;为了保证数据的强一致性,InnoDB使用了强互斥锁来保证同一行记录的修改和删除的顺序性;InnoDB使用插入意向锁,可以提高插入并发性;【另一种情况】假设不是插入并发,而是读写并发,结果会怎样?MySQL、InnoDB,默认隔离级别(RR)。t(iduniquePK,名称);数据表中有数据:10,shenjian20,zhangsan30,lisi先执行事务A,查询了部分记录,还没有提交:select*fromtwhereid>10;事务B在第10和20行插入第一条记录后执行:insertintotvalues(11,xxx);这里:将使用什么锁?事务B会被阻塞吗?下面继续说实话和回答。【继续插播,铺垫知识】InnoDB的细粒度锁是在索引记录上实现的。如果查询没有命中索引,也会退化为表锁。【InnoDB索引】InnoDB索引有两种索引,聚集索引(ClusteredIndex)和普通索引(SecondaryIndex)。InnoDB中的每张表都会有一个聚簇索引:如果表定义了PK,那么PK就是聚簇索引;如果表没有定义主键,第一个非空唯一列是聚簇索引;否则,InnoDB将创建一个隐藏的row-id用作聚集索引;为了描述方便,下面以PK来描述。索引的结构是B+树。B+树的细节这里就不展开了,只做几点总结:(1)在索引结构中,非叶子节点存放键,叶子节点存放值;(2)聚簇索引,叶子节点存放行记录(row);画外音:因此,InnoDB的索引和记录是放在一起的,而MyISAM的索引和记录是分开存放的。(3)普通索引,叶子节点存放PK的值;画外音:因此,如果InnoDB的普通索引不满足索引覆盖率,它实际上会扫描两次:第一次pass,通过普通索引找到PK;第二遍,PK查找行记录;索引结构,InnoDB/MyISAM索引结构,如果大家感兴趣,以后会写一篇详细的文章。例如,假设有一个InnoDB表:t(idPK,nameKEY,sex,flag);表中有4条记录:1、shenjian、m、A3、zhangsan、m、A5、lisi、m、A9、wangwu、f、B可以看到:第一张图中idPK的聚簇索引,叶子存储所有行记录;第二张图,name上的commonindex,叶子里存放的是PK的值;for:select*fromtwherename='shenjian';PK=1会先在name的普通索引上查询;然后在聚簇索引上查询(1,shenjian,m,A)的行记录;有了上面的铺垫,下面继续介绍InnoDB剩下的三种锁:RecordLocks;间隙锁;下一键锁;为方便起见,除非另有说明,否则默认的事务隔离级别是重复读取(RepeatedRead,RR)。第五种,记录锁(RecordLocks)记录锁,阻塞索引记录,例如:select*fromtwhereid=1forupdate;它会锁定id=1的索引记录,以防止其他事务对该行插入、更新和删除id=1。需要说明的是:select*fromtwhereid=1;是快照读(SnapShotRead),不加锁,在《InnoDB并发如此高,原因竟然在这?》中有详细解释。第六种,间隙锁(GapLocks)间隙锁,阻塞索引记录中的区间,或者第一条索引记录之前的范围,或者最后一条索引记录之后的范围。还是上面的例子,InnoDB,RR:t(idPK,nameKEY,sex,flag);表中有4条记录:1、shenjian,m,A3,zhangsan,m,A5,lisi,m,A9,wangwu,f,B,SQL语句select*fromtwhereidbetween8and15forupdate;会阻塞区间,防止插入其他事务id=10的记录。画外音:为什么要阻止插入id=10的记录?如果插入成功,第一个事务执行相同的SQL语句,你会发现结果集中多了一条记录,也就是幻像数据。间隙锁的主要目的是防止其他事务在间隙中插入数据,造成“不可重复读”。如果事务的隔离级别降为ReadCommitted(RC),间隙锁会自动失效。第七种,Next-KeyLocks(下一键锁),是记录锁和间隙锁的结合。它的阻塞范围包括索引记录和索引区间。更具体地说,临时键锁会阻塞索引记录本身,以及索引记录之前的间隔。如果一个session对索引记录R持有共享锁/排他锁,其他session不能立即在R之前的区间插入新的索引记录。画外音:原文说如果一个session对某个索引中的记录R持有共享锁或排它锁,另一个会话不能在索引顺序中R之前的间隙中插入新的索引记录。还是上面的例子,InnoDB,RR:t(idPK,nameKEY,sex,flag);表中有4条记录:1,shenjian,m,A3,zhangsan,m,A5,lisi,m,A9,wangwu,f,BPK上的潜在keylocks为:(-infinity,1](1,3](3,5](5,9](9,+infinity)键锁的主要目的也是为了避免幻读(PhantomRead)。如果事务隔离级别降级为RC的话,键锁也会失败。画外音:关于事务和幻读的隔离级别,之前的文章没有说明,如果有兴趣,后面会详细说明。【总结】(1)自动增量锁(Auto-incLocks):表级锁,专门插入到AUTO_INC列中用于事务处理。如果插入位置发生冲突,会阻塞多个事务,保证数据的一致性;(2)共享/排他锁(SharedandExclusiveLocks):行级锁、S锁和X锁、强锁;(3)意向锁:表级锁,IS锁和IX锁,弱锁,只表示意向;(4)插入意向锁(InsertIntentionLocks):对于插入,如果插入位置不冲突,则不会阻塞多个事务,提高插入并发性;(5)记录锁(RecordLocks):对索引记录加锁,对索引记录实现互斥,保证数据的一致性;(6)间隙锁(GapLocks):阻塞索引记录中间的区间,RR下有效,防止区间被其他事务插入;(7)临时键锁(Next-keyLocks):块索引记录,以及索引记录之间的间隔,RR下有效,防止幻读;InnoDB锁,涉及索引类型,事务隔离级别,更多,更复杂,更有趣的案例,后续为大家介绍。【本文为专栏作者《58神剑》原创稿件,转载请联系原作者】点此阅读更多该作者好文
