文章来源:   作者:何丽花     发布时间:2022-08-02 12:57    浏览量:

1、锁的类型

InnoDB实现了如下两种标准的行级锁:

共享锁S Lock):允许事务对一条行数据进行读取

排他锁X Lock):允许事务对一条行数据进行删除或更新

如果一个事务T1已经获得了行r的共享锁,那么另外的事务T2可以立即获得行r的共享锁,因为读取并没有改变行 r 的数据, 称这种情况为锁兼容 (Lock Compatible)。但若有其他的事务T3想获得行r的排他锁,则其必须等待事务T1, T2释放行r上的共享锁——这种情况称为锁不兼容。因为获取排他锁一般是为了改变数据,所以不能同时进行读取或则其他写入操作。

从上可以发现,X锁与任何锁都不兼容,而S锁仅和S锁兼容

此外, InnoDB 存储引擎支持多粒度锁定, 这种锁定允许事务在行级上的锁和表级上的锁同时存在。为了支待在不同粒度上进行加锁操作, InnoDB 存储引擎支持 一种额外的锁方式, 称之为意向锁 (Intention Lock)。意向锁是将锁定的对象分为多个层次, 意向锁意味着事务希望在更细粒度上进行加锁。

InnoDB存储引擎支持意向锁设计比较简练,其意向锁即为表级别的锁。设计目的主要是为了在事务中揭示下一行将被请求的锁类型。其支持两种意向锁:

意向共享锁IS Lock):事务想要获得一张表中某几行的共享锁

意向排他锁IX Lock):事务想要获得一张表中某几行的排他锁

由于InnoDB存储引擎支持的是行级别的锁,因此意向锁不会阻塞除全表扫描以外的任何请求,它们的主要目的是为了表示是否有人请求锁定表中的某一行数据。

如果没有意向锁,当已经有人使用行锁对表中的某一行进行修改时,如果另外一个请求要对全表进行修改,那么就需要对所有的行是否被锁定进行扫描,在这种情况下,效率是非常低的;不过,在引入意向锁之后,当有人使用行锁对表中的某一行进行修改之前,会先为表添加意向互斥锁(IX),再为行记录添加互斥锁(X),在这时如果有人尝试对全表进行修改就不需要判断表中的每一行数据是否被加锁了,只需要通过等待意向互斥锁被释放就可以了。

2、锁的算法

InnoDB有三种行锁的算法:

Record Lock

简单说就是单个行记录上加锁,防止事务间修改或删除数据。Record Lock总是会去锁住索引记录,如果表建立的时候没有设置任何一个索引,InnoDB存储引擎会使用隐式的主键来进行锁定。

Gap Lock

间隙锁,表示只锁住一段范围,不锁记录本身,通常表示两个索引记录之间,或者索引上的第一条记录之前,或者最后一条记录之后的锁。

Next-Key Lock

Gap Lock + Record Lock,锁定一个范围及锁定记录本身。例如一个索引有10, 11, 13, 20这四个值,那么该索引可能被Next-key Locking的区间为(负无穷, 10), (10, 11), (11, 13), (12, 20), (20, 正无穷)。需要理解一点,InnoDB中加锁都是给所有记录一条一条加锁,并没有一个直接的范围可以直接锁住,所以会生成多个区间。

MySQL默认情况下使用RR的隔离级别,而Next-key Lock正是为了解决RR隔离级别下的不可重复读问题和幻读问题。所谓不可重复读就是一个事务内执行相同的查询,会看到不同的行记录,在RR隔离级别下这是不允许的。

假设索引上有记录1,4,5,8,12,我们执行类似语句SELECT … WHERE col > 10 FOR UPDATE。如果我们不在(8, 12)之间加上Next-key Lock,另外一个会话就可能向其中插入一条记录9,再执行一次相同的SELECT ... FOR UPDATE,就会看到新插入的记录。这也是为什么MySQL插入一条记录时,需要判断下一条记录上是否加锁了,如果加锁就需要等待。

InnoDB对行的查询默认采用Next-key算法。然而,当查询条件为等值时,且索引有唯一属性时(就是只锁定一条记录),InnoDB存储引擎会对Next-Key Lock进行优化,将其降级为Record Lock,即仅锁住索引本身,而不是一个范围,因为此时不会产生重复读问题。

3、锁读取

一致性非锁定读(consistent nonlocking read)是指InnoDB存储引擎通过多版本控制(MVVC)读取当前数据库中行数据的方式。如果读取的行正在执行DELETE或UPDATE操作,这时读取操作不会因此去等待行上锁的释放。相反地,InnoDB会去读取行的一个快照。

 

上图直观地展现了InnoDB一致性非锁定读的机制。之所以称其为非锁定读,是因为不需要等待行上排他锁的释放。快照数据是指该行的之前版本的数据,每行记录可能有多个版本,一般称这种技术为行多版本技术。由此带来的并发控制,称之为多版本并发控制(Multi Version Concurrency Control, MVVC)。InnoDB是通过undo log来实现MVVCundo log本身用来在事务中回滚数据,因此快照数据本身是没有额外开销。此外,读取快照数据是不需要上锁的,因为没有事务需要对历史的数据进行修改操作。

一致性非锁定读是InnoDB默认的读取方式,即读取不会占用和等待行上的锁。但是并不是在每个事务隔离级别下都是采用此种方式。此外,即使都是使用一致性非锁定读,但是对于快照数据的定义也各不相同。

在事务隔离级别READ COMMITTED和REPEATABLE READ下,InnoDB使用一致性非锁定读。然而,对于快照数据的定义却不同。

在默认情况下,即事务的隔离级别是repeatable read模式下,InnoDB存储引擎的SELECT操作使用的是一致性非锁定读。但是在某些情况下,用户需要显示的读取数据操作进行加锁保证数据逻辑的一致性。

InnoDB提供了两种方式实现一致性锁定读:

select … for udpate,对读取的行加了X锁

select … lock in share mode,对读取的行加了S锁

需要注意的是,以上两种语句必须在一个事务当中,当事务提交了,锁也就释放了。

4、阻塞

因为不同锁之间的兼容性关系,有时候一个事务中的锁需要等待另一个事务中的锁释放它所占用的资源,这就是阻塞

InnoDB中,参数innodb_lock_wait_timeout用来控制等待的时间,innodb_rollback_on_timeout用来设定是否在等待超时后回滚。前者是动态的,后者是静态的。

mysql> show variables like 'innodb_lock_wait_timeout'/G;
*************************** 1. row
Variable_name: innodb_lock_wait_timeout
Value50
mysql> show variables like 'innodb_rollback_on_timeout'/G;
1. row
: innodb_rollback_on_timeout
Value: OFF

5、死锁

死锁是指两个或两个以上的事务在执行过程中,因争夺资源而造成的一种相互等待的现象。若无外力作用,事务都将无法推进下去。

解决死锁做简单的方法就是超时,即当两个事务互相等待时,当一个等待时间超过了某一阈值,其中一个事务进行回滚,另一个等待的事务就能继续进行。

但是如果超时的事务所占权重比较大,如事务更新了很多行,占用了较多的undo log,回滚这个事务的时间相对于另一个事务所占用的时间可能会更多,就显得不合适了。

因此,除了超时机制,当前数据库都普遍采用等待图wait-for graph)的方式来进行死锁检测。

wait-for graph要求数据库保存以下两种信息:

锁的信息链表

事务等待链表

通过上述链表可以构造出一张图,而在这个图中若存在回路,就代表存在死锁,因此资源间相互发生等待。在 wait-for graph中,事务为图中的节点。而在图中,事务T1指向T2边的定义为:

事务T1等待事务T2所占用的资源

事务T1最终等待T2所占用的资源,也就是事务之间在等待相同的资源,而事务T1发生在事务T2的后面


甘肃华科信息技术有限责任公司    版权所有    陇ICP备17001897-1号   甘公网安备 62010202001329 号