InnoDB存储引擎中的锁

一,InnoDB中的锁

1,锁的类型

InnoDB存储引擎实现了以下两种标准的行级锁:

共享锁,允许事务读一行数据。

排它锁,允许事务删除或更新一条数据。

如果一个事务t1已经获得了行r的共享锁,那么另外的事物T2可以立即获得行r的共享锁,因为读取并没有改变行R的数据,称这种情况为锁兼容。

但若有其他事物想要获取行r的排它锁,则其必须等待事务t1,t2释放行r上的共享锁,这种情况称为锁不兼容。

XS
X不兼容不兼容
S不兼容兼容

X锁与任何锁都不兼容,而S锁仅仅与S锁兼容,需要特别注意的是,S与X都是行锁,兼容是指对同一记录锁的兼容性情况。

此外,InnoDB存储引擎支持多粒度锁定,这种锁定允许事务在行级上的锁和表级上的锁同时存在,为了支持在不同粒度上进行加锁操作,InnoDB存储引擎支持一种额外的锁方式,称之为意向锁。

意向锁是将锁定的对象分为多个层次,意向锁意味着事务希望在更细粒度上进行加锁。

若将上锁的对象看成是一棵树,那么对最下层的对象上锁,也就是对最细粒度的对象进行上锁,那么首先需要对粗粒度的对象上锁,如果需要对页上的记录r进行上X锁,那么分别需要对数据库A,表,页上意向锁IX,最后对记录r上X锁。若其中任何一个部分导致等待,那么该操作需要等待粗粒度锁的完成。

举例来说,在对记录r加X锁之前,已经有事务对表1进行了S表锁,那么表1上已存在S锁,之后事务需要对记录r在表1上加上IX,由于不兼容,所以该事务需要等待表锁操作的完成。

在这里插入图片描述

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

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

2)意向排它锁:事务想要获得一张表中某几行的排它锁。

由于InnoDB存储引擎支持的是行级别的锁,因此意向锁其实不会阻塞除全表扫描以外的任何请求。故表级意向锁与行级意向锁的兼容性如下:

ISIXSX
IS兼容兼容兼容不兼容
IX兼容兼容不兼容不兼容
S兼容不兼容兼容不兼容
X不兼容不兼容不兼容不兼容

用户可以通过命令SHOW ENGINE INNODB STATUS命令来查看当前锁请求的信息

InnoDB1.0版本之前,用户只能通过命令SHOW FULL PROCESSLIST,SHOW ENGINE INNODB STATUS等来查看当前数据库中锁的情请求,然后再判断事务锁的情况。1.0开始,在INFORMATION_SCHEMA架构下添加了表INNODB_TRX,INNODB_LOCKS,INNODB_LOCK_WAITS.通过这三张表,用户可以更简单的监控当前事务并分析可能存在的锁问题。

1)INNODB_TRX

字段名说明
trx_idInnodb存储引擎内部唯一的事务ID
trx_state当前事务的状态
trx_started事务的开始时间
trx_requested_lock_id等待事务的锁ID,如trx_state的状态为LOCK_WAIT,那么该值代表当前的事务等待之前事务占用锁资源的ID。若trx_state不是LOCK WAIT,则该值为NULL。
trx_wait_started事务等待开始的时间
trx_weight事务的权重,反映了一个事物修改和锁住的行数,在InnoDB存储引擎中,当发生死锁需要回滚时,InnoDB存储引擎该值最小的进行回滚。
trx_mysql_thread_idMySQL中的线程ID,SHOW PROCESSLIST显示的结果
trx_query事务运行的SQL语句

2)INNODB_LOCKS

字段名说明
lock_id锁的ID
lock_trx_id事务的ID
lock_mode锁的模式
lock_type锁的类型,表锁还是行锁
lock_table要加锁的表
lock_index锁住的索引
lock_space锁对象的space_id
lock_page事务锁定页的数量,若是表锁,则该值为NULL
lock_rec事务锁定行的数量,若是表锁,则该值为NULL
lock_data事务锁定记录的主键值,若是表锁,则该值为NULL

注意:lock_data这个值并非是可信值。例如当前用户运行一个范围查找时,lock_data可能只返回第一行的主键值。与此同时,如果当前资源被锁住了,若锁住的页因为InnoDB存储引擎缓冲池的容量,导致该页从缓冲池中被刷出,则查看INNODB_LOCKS表时,该值同样会显示NULL,即InnoDB存储引擎不会从磁盘进行再一次的查找。

通过表INNODB_LOCKS查看了每张表上锁情况后,用户就可以来判断由此引发的等待情况了。当事务较小时,用户就可以认为地,直观的进行判断了。但是当事务量非常大,其中锁和等待也时常发生,这个时候就不那么容易判断,但是通过表INNODB_LOCKS_WAITS。可以很直观的反映当前事务的等待。

3)INNODB_LOCKS_WAITS

字段说明字段说明
requesting_trx_id申请锁资源的事务IDblocking_trx_id阻塞的事务ID
requesting_lock_id申请的锁的IDblocking_trx_id阻塞的锁的ID

2,一致性非锁定读

一致性的非锁定读是指InnoDB存储引擎通过行多版本控制的方式来读取当前执行时间数据库中行的数据。

如果读取的行正在执行DELETE或UPDATE操作,这时读取操作不会因此等待行上锁的释放。相反的,InnoDB存储引擎会去读取行的一个快照数据。

在这里插入图片描述

该图直观的展示了InnoDB存储引擎一致性的非锁定读。之所以称其为非锁定读,因为不需要等待访问的行上X锁的释放,快照数据是指该行的之前版本的数据,该实现是通过undo段来完。而undo用来在事务中回滚数据,因此快照数据本身是没有额外的开销。此外,读取快照数据是不需要上锁的,因为没有事务需要对历史的数据进行修改操作。

可以看到非锁定读机制极大的提高了数据库的并发性。在InnoDB存储引擎的默认设置下,这是默认的读取方式,即读取不会占用和等待表上的锁。但是在不同的事务隔离级别下,读取的方式不同,并不是在每个事务隔离级别下都是采用非锁定的一致性读,此外,即使都是使用非锁定的一致性读,但是对于快照数据的定义也各不相同。

快照数据其实就是当前行数据之前的历史版本,每行记录可能有多个版本。一个行记录可能不只有一个快照数据,一般称这种技术为多版本技术,由此带来的并发控制,称之为多版本并发控制(MVCC)。

在事务隔离级别读已提交和可重复读(默认)下,InnoDB存储引擎使用非锁定的一致性读。然而,对于快照数据的定义却不相同。

在读已提交事务隔离界别下,对于快照数据,非一致性读总是读取被锁定行的最新一份快照数据。而在可重复读事务隔离级别下,对于快照数据,非一致性读总是读取事务开始时的行数据版本。

首先在MySQL数据库的连接会话A中执行如下SQL语句:

在这里插入图片描述

会话A中已通过显式的执行命令BEGIN开启了一个事务,并读取了表中id=1的数据,但是事务并没有结束。与此同时,用户在开启另一个会话B,这样可以模拟并发的情况,然后对会话B做如下操作:

在这里插入图片描述

再会话B中奖事务表中id=1的记录修改为id=3,但是事务同样没有提交,这样id=1的行其实加了一个X锁。这时如果在会话A中再次读取id为1的记录,根据InnoDB存储引擎的特性,即在读已提交和可重复度对的事务隔离级别下会使用非锁定的一致性读。回到之前的会话A,接着上次未提交的事务,执行SQL语句select * from parent where id =1的操作,这时不管使用读已提交还是可重复度的事务隔离级别,显示数据应该都是

在这里插入图片描述

由于当前id=1的数据被修改了1次,因此只有一个行版本的记录。接着,在会话B中提交上次的事务

在这里插入图片描述

在会话B提交事务后,这时在会话A中在运行select * from parent where id =1的操作,在读已提交和可重复度的事务隔离级别下得到结果就不一样了。对于读已提交的事务隔离级别下,他总是读取行的最新版本,如果行被锁定了,则读取该行版本的最新一个快照。在上述例子中,因为会话B已经提交了了事务,所以读已提交事务隔离级别下会得到如下结果:

在这里插入图片描述

而对于可重复读,总是读取事务开始时的行数据,因此对于可重复度,得到的结果是:

在这里插入图片描述

3,一致性锁定读

在默认配置下,事务的隔离级别为可重复读,innodb会使用一致性非锁定读,但是在某些情况下,用户需要显示的对数据库操作进行加锁以保证数据逻辑的一致性。而这要求数据库支持加锁语句,即使是对于select的只读操作。Innodb存储引擎对于select语句支持两种一致性的锁定读操作。

select…for update

select…lock in share mode

select …for update 对读取的行记录加一个X锁,其他事物不能对已经锁定的行加上任何锁。

select…lock in share mode对读取的行记录加一个S锁,其他事物可以向被锁定的行加S锁,但是如果加X锁,则会被阻塞。

对于一致性非锁定读,即使读取的行已经被执行了select…for update ,也是可以进行读取的。

此外,select…for update ,select…lock in share mode必须在一个事务中,当事务提交了,锁也就释放了。因此在使用上述两句select锁定语句时,必须加上begin,start transaction或者set autocommit=0。

4,自增长与锁

在InnoDB存储引擎的内存结构中,对于每个含有自增长值的表都有一个自增长计数器。当对含有自增长的计数器的表进行插入操作时,这个计数器会被初始化,执行select max(auto_inc_col) from t for update来得到计数器的值。

这是一种特殊的表锁,实际上效率并不高,因为虽然完成插入以后立即释放锁,但是批量插入下性能并不好。事务必须等待前一个插入的完成,否则将会被阻塞。

从MySQL5.1.22,innod提供了一种轻量级互斥量的自增长实现机制,大大提高了自增长值插入的性能。从该版本开始,innodb存储引擎提供了一个参数innodb_autoinc_lock_mode来控制自增长的模式,默认值为1.

注意:InnoDB存储引擎中自增长的实现和MyIsam不同,myisam存储引擎是表锁设计,自增长不用考虑并发插入问题。因此在master上用innodb存储引擎,在slave上用myisam存储引擎的replication架构下,用户必须考虑这种情况。

在InnoDB存储引擎中,自增长值的列必须是索引,同时必须是索引的第一个列,否则抛异常,而myisam并没有这个问题。

二,锁的算法

1,行锁的3种算法

InnoDB存储引擎有三种行锁的算法,分别是:

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对于行的查询都是采用这种算法。

例如有一个索引有10,11,13,20这四个值,那么该索引可能被next-key locking 的区间为:

在这里插入图片描述

2,解决Phantom Problem

在默认的事务隔离级别下,即可重复读下,innodb存储引擎采用next-key locking机制来避免幻像问题。这点可能不同于其他数据库。

幻像问题是指在同一事务下,连续执行两次同样的SQL语句可能导致不同的结果,第二次的SQL语句可能会返回之前不存在的行。

表T由1 2 5这三个值组成,若这时事务执行如下的SQL语句:select * from T where a>2 from update。

这时事务T1并没有进行提交操作,上述应该返回5这个结果。若与此同时,另一个事务T2插入了4这个值,并且数据库允许该操作,那么事务T1再次执行上述SQL语句会得到结果4和5.这与第一次得到的结果不同,违反了事务的隔离性,即当前事务能够看到其他事物的结果。

innodb存储引擎采用了NEXT-KEY LOCKING的算法避免幻像问题。对于上述SQL语句,他锁住的不是5这单个值,而是对(2,+无穷)这个范围加了X锁。因此任何对于这个范围的插入都是不被允许的,从而避免了幻像问题。

innodb存储引擎默认的事务隔离级别是可重复读,在该隔离级别下,其采用Next-key Locking 的方式来加锁。而在事务隔离级别读已提交下,其仅仅采用record lock,因此上述问题的演示,需要将隔离级别设置为读已提交。

三,锁问题

通过锁定机制可以实现事务的隔离性要求,使得事务可以并发的工作。锁提高了并发,但是却会带来潜在的问题。不过好在因为事务隔离性的要求,锁只会带来三种问题,如果可以防止这三种情况,那将不会产生并发异常。

1,脏读

一个事务可以读到另一个事物未提交的数据。

读未提交隔离级别下,会产生脏读。

2,不可重复度

一个事务在第一次读取数据和第二次读取数据的过程中,另一个事务对数据进行了修改,并提交,导致两次读到的结果不一致。

读已提交的隔离级别下,会有不可重复度的问题。

3,丢失更新

一个事务的更新操作被另一个事务的更新操作所覆盖。

1)事务1更改了一行记录

2)事务2也更改了这行记录

3)事务1提交

4)事务2提交

四,阻塞

因为不同锁之间的兼容性关系,在有些时刻一个事务中的锁需要等待另一个事物中的锁释放他锁占用的资源,这就是阻塞,阻塞并不是一件坏事,他是为了保证事务可以并发且正常的运行。

默认情况下innodb不会回滚超时引发的错误异常,其实innodb存储引擎在大部分情况下都不会对异常进行回滚。如在一个会话中执行了如下语句:

在这里插入图片描述

在会话A中开启了一个事务,在Next-key Lock算法下锁定了小于4的所有记录(其实也是锁定了4这个记录本身)。在另一个会话B中执行如下语句:

在这里插入图片描述

可以看到,在会话B中插入记录5是可以的,但是在插入记录3时,因为会话A中算法的关系,需要等待会话A中事务释放这个资源,所以等待后产生了超时。但是在超时后用户在进行select操作时会发现,5这个记录依然存在。

这是因为这是会话B中的事务虽然抛出异常,但是既没有进行commit操作,也没有进行rollback。而这是十分危险的状态,因此用户必须判断是否需要commit还是rollback,之后在进行下一步的操作。

五,死锁

1,死锁的概念

死锁是指两个或两个以上的事务在执行过程中,因为争夺锁资源而造成的一种互相等待的现象。

解决死锁最简单的一个方法是超时,如果超过等待时间,回滚。当然还要考虑权重等等。

此外,innodb还采用了等待图wait-for-graph的方式来进行死锁检测。

2,死锁的概率

系统中事务的数量越多,发生死锁的概率越大。

每个事务操作的数量越多,发生死锁的概率越大。

操作数据的集合越小,发生死锁的概率越大。

六,锁升级

锁升级是指将当前锁的粒度降低。举例来说,数据库可以吧一个表的1000个行锁升级为一个页锁,或者将页锁升级为表锁。如果在数据库的设计中认为锁是一种稀有资源,而且想避免锁的开销,那数据库会频繁出现锁升级的现象。

innodb存储引擎不存在锁升级的问题。因为其不是根据每个记录来产生行锁的,相反,其根据每个事务访问的每个页对锁进行管理的,采用的是位图的方式。因此不管一个事务锁住页中的一个记录还是多个记录,其开销通常都是一致的。

已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 游动-白 设计师:白松林 返回首页