其他分享
首页 > 其他分享> > Innodb存储引擎之锁

Innodb存储引擎之锁

作者:互联网

Innodb存储引擎之锁

一、概述

数据库需要尽可能的提高并发访问效率,还要能确保每个用户能以一致的方式读取和修改数据,根据此问题诞生了锁机制。

锁是数据库系统区别于文件系统的一个非常重要的特性,它用于管理对共享资源的并发访问,保证各个用户访问数据一致和完整。

Innodb 提供一致性的非锁定读,支持行级锁。

二、lock 与 latch

在这里插入图片描述

三、Innodb存储引擎中的锁

Innodb支持如下两种标准的行级锁

仅S 锁跟 S锁 兼容,X 锁跟任意锁不兼容。

Innodb支持多粒度的锁,这种锁定允许事务在行级上的锁和表级上的锁同时存在。

为了支持不同粒度的锁,Innodb支持一种额外的锁定方式,称之为意向锁,它将数据对象分为多个层次,意向锁的意思是希望再更细的粒度上加锁。

下图为数据库的对象层次:

在这里插入图片描述
如上的树形结构中,如果要对树底层的行记录加锁,那么必须依次对它所在的数据库、表、页上加一个意向锁。意向锁是表级别的锁,它预示着即将被请求的锁的类型。因为Innodb支持的是行级别的锁,所以意向锁不会阻塞除全表扫描<表级别非意向锁>以外的任何请求。
下图为事务之间表级意向锁和行级锁的兼容情况:
在这里插入图片描述

事务T1 对表A的编号为k的行,加一个X锁,那么需要先对表和所在页加意向锁,从兼容情况可以得知,任意的意向锁之间相互兼容,最后判断编号为k的行记录上是否存在任意锁,如果不存在,T1对其上X锁成功,否则等待。

可以通过information_schema架构下的三张表简单的分析当前事务可能存在的锁问题

下图为我模拟的事务等待场景,17270事务占有了锁,事务17272必须等待。
在这里插入图片描述

一致性非锁定读

一致性非锁定读是Innodb通过对行记录的多版本控制方式来实现的,它依赖于undo<它用来支持事务的回滚>段来实现。所谓一致性非锁定读,即使需要读取的行上存在X锁,读取的请求也不会进行等待,而是去读取,通过undo段实现的,行记录的一个快照。它不会等待,所以称为 “非锁定”,因为undo段上的快照数据是不会被事务修改的,所以不需要上锁。

另外由于undo段上的行记录的快照可能不止一个,所以这种多版本控制称为行多版本技术,它带来的并发控制称为:多版本并发控制(MVCC);

Innodb在一些事务级别下支持一致性非锁定读,此外即使是在这些事务隔离级别下,对于一致性非锁定读也有着差异;Innodb中,READ COMMITED 和 REPEATABLE READ 事务隔离级别下支持一致性非锁定读。

一致性锁定读

Innodb只是显式的对数据库操作进行加锁以确保数据一致,它支持如下两种形式的一致性锁定读。

for update - 它对读取的行加一个X锁。
lock in share mode - 他对读取的行加一个S锁。

通过锁兼容性,我们很清楚能对它们做什么,不能做什么。

自增长与锁

自增长相信大家都很熟,它在主键上的应用非常广泛;在较低版本的MySQL中,自增长通过表锁实现,这样做的缺点是并发性能较差,因为它是表锁的设计,所以当多个事务插入时,别的事务只能等待执行的事务提交后才能继续。

先来了解一下SQL插入语句类型:

从MySQL5.1.22版本开始,Innodb存储引擎引入了一种轻量级的互斥量(同一时刻只能有一个线程能访问它,相较于表锁它需要付出的代价小了太多)来实现自增长;此版本开始Innodb提供了一个参数:innodb_autoinc_lock_mode 来控制自增长的模式。

外键与锁

与Oracle不同的是,Innodb建立外键时,如果用户没有主动建立索引,它会自动在该列上建一个索引。
当需要对表上的外键值进行更新或者插入时,会通过一致性锁定读的方式去查父表该外键是否存在于父表上:

如果存在一个行记录,会对该行记录加一个S锁,在对子表操作事务提交前,父表上该行不能被任何事务上X锁。

从而保证了外键约束的功能。

四、锁的算法

锁的算法

  1. Record Lock 单个行记录上的锁,它总是用于锁定索引上的一个记录。

  2. Gap Lock 间隙锁,锁定一个范围,但是不包含记录本身,它的作用是阻止多个事务将记录插入到同一个范围内导致幻读问题。

  3. Next-Key Lock 它锁定的不是单个值而是一个范围,且包含它自身,** Innodb 通过它解决大名鼎鼎的 “幻读问题” **。它除了锁定包含该值的范围外,还会锁定该值的下一个值所在的区间。

重点讲一下Next-Key Lock,假如一个索引上有如下值:-100,-30,22,56,那么可以被锁定的范围是:

此外,当上述索引为唯一索引时,锁会降级为行锁,此时还是对行记录 56 上锁,此时被上锁的就是56这个行本身。【例外情况是,当唯一索引由多个列组成,如果事务中只操作其中的部分列时,那么此时的查询类型是range而不是point查询,所以依然需要使用 Next-Key Lock】。

当存在多个索引时,会对聚集索引上的Next-Key Lock 降级为 Record Lock 而对于非唯一的辅助索引则会严格按照Next-Key Lock 上锁,

Phantom Problem 幻读问题

下面来进行一个模拟,表r中有三行数据,其中 列a 上存在一个聚集索引。

测试1,使用read committed 事务隔离级别:

表中原始数据如下
在这里插入图片描述
事务A执行如下语句不提交事务

set transaction isolation level read committed;
start transaction;
select * from r where a >= 10 for update;

此时开启事务B,执行如下语句,插入成功
在这里插入图片描述

回到事务A,再次执行相同的查询语句,发现查询结果多了一列:12,很明显它违反了事务的隔离性,因为当前事务能看到别的事务的执行结果,这就是Phantom Problem 问题。
在这里插入图片描述

测试2,使用 repeatable read 事务隔离级别

表中数据同样为:10、13、15

事务A执行如下语句不提交事务,由于隔离级别不同,此时关于 行10 的锁不再降级为行锁

set transaction isolation level repeatable read;
start transaction;
select * from r where a >= 10 for update;

事务B 执行如下插入,此时发现,插入任意 大于10 的值都被阻塞。
在这里插入图片描述

在这里插入图片描述

这时任意可能导致事务A中SQL select * from r where a >= 10 for update; 查询结果变更的SQL都无法成功插入。

  1. 此外测试过程中还发现了一些特殊现象,当被锁定的值是边界的时候可能不一定完全遵循上述规则。

repeatable read 事务隔离级别下,并不一定完全是Next-Key Lock,它锁定的范围是根据SQL条件来决定的。例如:表中a有如下值:10,20,30,40,50

五、锁的问题

脏读

所谓脏读,是指事务读到了别的事务未提交的数据,被当前事务读到,在事务隔离级别为:read uncommitted 下会出现,它违反了事务隔离性。

在mysql 主从模式下,slave节点可以设置为:read uncommitted 因为slave只负责查询,并不会有需要修改数据的事务由slave去完成。

不可重复读

指同一个事务中,对相同资源的多次读取可能得到不同的结果,这是因为需要读取的数据被别的事务修改了,同样这也违反了事务的隔离性。在 read commited 事务隔离级别下会出现该问题。

它读到的都是已经提交的数据<非锁定读,否则别的事务不可能更改目标资源>,但是当前事务未提交前,目标资源还可能被别的事务读取并修改。

丢失更新

该问题与事务隔离级别无关,它是逻辑上的丢失,实际应用中非常多这样的使用场景,我们需要对某个行进行更新,但是我们不是直接update 去更新,而是先查询,然后在现有指的基础上修改。

在并发环境下,如果多个用户执行上述操作就会出现问题。

针对此种情况,需要在读取的时候就对数据加一个 X 锁 (select … for update ),这样就可以保证该数据不会别别的事务读取并修改了。

六、阻塞

阻塞发生的原因是由锁兼容问题导致的,例如事务A在行 K 上加了一个X 锁,其它任意事务访问行 K 的行为都会被阻塞,如此可以保证事务可以并发且正确的运行。

需要注意的是,一旦事务中发生了异常,必须决定回滚还是提交。

七、死锁

死锁的概念很简单:两个事务互相等待对方互斥占用的资源,或者多个事务间互斥资源的请求形成了回路。

事务死锁判断机制名为:wait-for graph,它是一种主动死锁检测机制,如果它发现某个sql 可能导致死锁会抛出异常。它的基本概念可以参考 JVM 的 GC Root机制判断循环依赖,当然它们具体实现肯定各不相同。

八、锁升级

锁升级指,锁从细粒度升级未粗粒度,多个行锁可能升级为页锁,多个页锁也可能升级为表锁。

标签:之锁,事务,lock,存储,插入,Innodb,Lock,锁定
来源: https://blog.csdn.net/bokerr/article/details/120638797