文章目录
InnoDB 行锁的3种算法
InnoDB存储引擎有3种行锁的算法,其分别是:
❑Record Lock:单个行记录上的锁
❑Gap Lock:间隙锁,锁定一个范围,但不包含记录本身
❑Next-Key Lock∶Gap Lock+Record Lock,锁定一个范围,并且锁定记录本身
Record Lock总是会去锁住索引记录,如果InnoDB存储引擎表在建立的时候没有设置任何一个索引,那么这时 InnoDB存储引擎会使用隐式的主键来进行锁定。 Next-Key Lock是结合了Gap Lock和Record Lock的一种锁定算法,在Next-Key Lock算法下,InnoDB对于行的查询都是采用这种锁定算法。
在InnoDB 默认的事务隔离级别下,即REPEATABLE READ下,InnoDB存储引擎采用Next-Key Locking这种锁定 算法。例如一个索引有10,11,13和20这四个值,那么该索引可能被Next-Key Locking的区间为: (-∞,10] (10,11] (11,13] (13,20] (20,+∞)
当查询的索引含有唯一属性时,InnoDB存储引擎会对Next-Key Lock进行优化,将其降级为 Record Lock,即仅锁住索引本身,而不是范围。 什么是唯一属性,其实就是我们所说的能够标识该行数据唯一的标识。 unique 字段。比如:主键就是唯一的,不重复的。我们也可以自己设计多个字段组合不重 复,唯一的。
DROP TABLE IF EXISTS t; CREATE TABLE t(a INT PRIMARY KEY); INSERT INTO t SELECT 1; INSERT INTO t SELECT 2; INSERT INTO t SELECT 5;
表t共有1、2、5三个值。在上面的例子中,在会话A中首先对a=5进行X锁定。而由于a是主 键且唯一,因此锁定的仅是5这个值,而不是(2,5)这个范围,这样在会话B中插入值4而不 会阻塞,可以立即插入并返回。即锁定由Next-Key Lock算法降级为了Record Lock,从而提高应用的并发性。
CREATE TABLE z(a INT,b INT, PRIMARY KEY(a),KEY(b)); INSERT INTO z SELECT 1,1; INSERT INTO z SELECT 3,1; INSERT INTO z SELECT 5,3; INSERT INTO z SELECT 7,6; INSERT INTO z SELECT 10,8;
表z的列b是辅助索引,若在会话A中执行下面的SQL语句: SELECT * FROM z WHERE b=3 FOR UPDATE 很明显,这时SQL语句通过索引列b进行查询,该列不是唯一属性,因此其使用传统的Next-Key Locking技术加锁,并且由于有两个索引,其需要分别进行锁定。对于聚集索引(primay-key a),其仅对列a等于5的索引加 上Record Lock。而对于辅助索引b,其加上的是Next- Key Lock,锁定的范围是(1,3)。特别需要注意的是, InnoDB存储引擎还会对辅助索引下一个键值加上gap lock,即还有一个辅助索引范围为(3,6)的锁。
SELECT * FROM z WHERE a = 5 LOCK IN SHARE MODE; INSERT INTO z SELECT 4,2; INSERT INTO Z SELECT 6,5;
第一个SQL语句不能执行,因为在会话A中执行的 SQL语句已经对聚集索引中列a=5的值加上X锁,因 此执行会被阻塞。
第二个SQL语句,主键插入4,没 有问题,但是插入的辅助索引值2在锁定的范围(1, 3)中,因此执行同样会被阻塞。第三个SQL语句, 插入的主键6没有被锁定,5也不在范围(1,3)之间。 但插入的值5在另一个锁定的范围(3,6)中,故同样 需要等待。
InnoDB 行锁 next-key lock 有什么作用
在默认的事务隔离级别下,即REPEATABLE READ下,InnoDB存储引擎采用Next-Key Locking机制来避免 Phantom Problem(幻读问题,也称不可重复读)。Phantom Problem是指在同一事务下,连续执行两次同 样的SQL语句可能导致不同的结果。(在 READ COMMITTED 事务隔离级别下会出现)
CREATE TABLE z(a INT,b INT, PRIMARY KEY(a),KEY(b)); INSERT INTO z SELECT 1,1; INSERT INTO z SELECT 3,1; INSERT INTO z SELECT 5,3; INSERT INTO z SELECT 7,6; INSERT INTO z SELECT 10,8;
在同一事务中,若此时执行语句: SELECT*FROM z WHERE b=3 FOR UPDATE 两次,中间间隔10秒时间执行。可以肯 定的说,我们会得到第三行数据的结果,即 (5,3)。此时我们知道,会有一个 Record Lock锁定主键 5,还会有一个 gap lock锁定 (1,3)和 (3,6)。
假设:我们分析下,若此时没有gap lock(1,3)和 (3,6),如果只有Record Lock锁定主键 5 会不会造成幻读。 分析:我们在第一次 select 完成之后,第二次select 之前,插入一天数据: INSERT INTO z SELECT 20,3; 这条数据是可 以插入成功的,因为我们只有一个record lock 锁定了 主键5,对于新插入的数据主键为 20 ,可以插入,且 无重复。 插入完成后,第二次 select 得到了两个值,(5,3)(20,3)。这就造成了同一事物中,第一次读取和第二次 读取的结果不一样,出现幻读。 如果有gap lock,插入就会被阻塞,不会出现幻读。
死锁
如何预防数据库死锁,生产环境如何避免死锁 MySQL 篇 死锁是指两个或两个以上的事务在执行过程中,因争夺锁资源而造成的一种互相等待的现象。若无外力作用,事 务都将无法推进下去。解决死锁问题最简单的方式是不要有等待,将任何的等待都转化为回滚,并且事务重新开 始。毫无疑问,这的确可以避免死锁问题的产生。然而在线上环境中,这可能导致并发性能的下降,甚至任何一 个事务都不能进行。而这所带来的问题远比死锁问题更为严重,因为这很难被发现并且浪费资源。 解决死锁问题最简单的一种方法是超时,即当两个事务互相等待时,当一个等待时间超过设置的某一阈值时,其 中一个事务进行回滚,另一个等待的事务就能继续进行。在InnoDB存储引擎中,参数innodb_lock_wait_timeout用 来设置超时的时间。 超时机制虽然简单,但是其仅通过超时后对事务进行回滚的方式来处理,或者说其是根据FIFO的顺序选择回滚对 象。但若超时的事务所占权重比较大,如事务操作更新了很多行,占用了较多的undo log,这时采用FIFO的方式, 就显得不合适了,因为回滚这个事务的时间相对另一个事务所占用的时间可能会很多。 因此,除了超时机制,当前数据库还都普遍采用wait-for graph(等待图)的方式来进行死锁检测。较之超时的解 决方案,这是一种更为主动的死锁检测方式。InnoDB存储引擎也采用的这种方式。wait-for graph要求数据库保存以下两种信息: 锁的信息链表和事务等待链表。
事务T1指向T2边的定义为:
❑事务T1等待事务T2所占用的资源
❑事务T1最终等待T2所占用的资源,也就是事务之间在等待相同的资源,而事务T1发生在事务T2的后面
wait-for graph是一种较为主动的死锁检测机制,在每个事务请求锁并发生等待时都会判断是否存在回路,若存在则有死锁,通常来说InnoDB存储引擎选择回滚undo量最小的事务。
意向锁
InnoDB存储引擎支持多粒度(granular)锁定,这种锁定允许事务在行级上的锁和表级上的锁同时存在。 为了支持在不同粒度上进行加锁操作,InnoDB存储引擎支持一种额外的锁方式,称之为意向锁(Intention Lock)。 意向锁是将锁定的对象分为多个层次,意向锁意味着事务希望在更细粒度(fine granularity)上进行加锁 InnoDB存储引擎支持意向锁设计比较简练,其意向锁即为表级别的锁。设计目的主要是为了在一个事务中揭示下 一行将被请求的锁类型。其支持两种意向锁:
1)意向共享锁(IS Lock),事务想要获得一张表中某几行的共享锁
2)意向排他锁(IX Lock),事务想要获得一张表中某几行的排他锁
由于InnoDB存储引擎支持的是行级别的锁,因此意向锁其实不会阻塞除全表扫以外的任何请求。
InnoDB存储引擎实现了如下两种标准的行级锁:
❑共享锁(S Lock),允许事务读一行数据。
❑排他锁(X Lock),允许事务删除或更新一行数据。
如果一个事务T1已经获得了行r的共享锁,那么另外的事务T2可以立即获得行r的共享锁,因为读取并 没有改变行r的数据,称这种情况为锁兼容(Lock Compatible)。但若有其他的事务T3想获得行r的排 他锁,则其必须等待事务T1、T2释放行r上的共享锁——这种情况称为锁不兼容。