详解MySql中InnoDB存储引擎中的各种锁


Posted in MySQL onFebruary 12, 2022

什么是锁

现实生活中的锁是为了保护你的私有物品,在数据库中锁是为了解决资源争抢的问题,锁是数据库系统区别于文件系统的一个关键特性。锁机制用于管理对共享资源的并发访。

数据库系统使用锁是为了支持对共享资源进行并发访问,提供数据的完整性和一致性

InnoDB存储引擎区别于MyISAM的两个重要特征就是:InnoDB存储引擎支持事务和行级别的锁,MyISAM只支持表级别的锁

InnoDB存储引擎中的锁

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

共享锁(S Lock),允许事务读一行数据排他锁(X Lock),允许事务删除或更新一行数据

锁的兼容性

- X S
X 不兼容 不兼容
S 不兼容 兼容

可以看到,X排他锁不与其他锁兼容,S共享锁只与S兼容

此外,InnoDB存储引擎支持多粒度(granular)锁定,这种锁定允许事务在行级上的锁和表级上的锁同时存在

为了支持在不同粒度上进行加锁操作,InnoDB存储引擎支持一种额外的锁方式,称之为意向锁(Intention Lock)。

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

详解MySql中InnoDB存储引擎中的各种锁

如上图,数据库从上到下可以分为数据库、表、页、记录四个层次,行记录是最细粒度的锁,我们在获取行锁的时候,需要从上到下各个级别分别进行锁定,最后才能获取到行锁。比如,你要获取行记录x的锁,需要先在数据库、表、页上加意向锁IX,其中任何一方需要等待锁,会造成行锁的等待

InnoDB存储引擎支持的意向锁即为表级别的锁。支持两种意向锁

  • 意向共享锁(IS Lock),事务想要获得一张表中某几行的共享锁
  • 意向排他锁(IX Lock),事务想要获得一张表中某几行的排他锁

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

- IS IX S X
IS 兼容 兼容 兼容 不兼容
IX 兼容 兼容 不兼容 不兼容
S 兼容 不兼容 兼容 不兼容
X 不兼容 不兼容 不兼容 不兼容

一致性非锁定读

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

如果读取的行正在执行DELETE或UPDATE操作,这时读取操作不会因此去等待行上锁的释放。

相反地,InnoDB存储引擎会去读取行的一个快照数据

快照数据是指该行的之前版本的数据,该实现是通过undo段来完成。而undo用来在事务中回滚数据,因此快照数据本身是没有额外的开销。此外,读取快照数据是不需要上锁的,因为没有事务需要对历史的数据进行修改操作

详解MySql中InnoDB存储引擎中的各种锁

快照数据其实就是当前行数据之前的历史版本,每行记录可能有多个版本。如上图,记录B就有多个历史的快照版本
这就是大名鼎鼎的MVCC

多版本并发控制(Multi Version Concurrency Control,MVCC)是指一个行记录有多个快照版本,由多个快照版本引发的并发控制,叫做多版本并发控制

那这么多历史的快速版本,访问的时候该用哪一个呢?

  • 在READ COMMITTED事务隔离级别下,非一致性读总是读取被锁定行的最新一份快照数据
  • 在REPEATABLE READ事务隔离级别下,非一致性读总是读取事务开始时的行数据版本

由此可见,不同的事务隔离级别在MVCC的处理上还不一样

一致性锁定读

在默认配置下,即事务的隔离级别为REPEATABLE READ模式下,InnoDB存储引擎的SELECT操作使用一致性非锁定读

但是在某些情况下,用户需要显式地对数据库读取操作进行加锁以保证数据逻辑的一致性

InnoDB存储引擎对于SELECT语句支持两种一致性的锁定读(locking read)操作:

  • SELECT…FOR UPDATE 对读取的行记录加一个X锁,其他事务不能对已锁定的行加上任何锁
  • SELECT…LOCK IN SHARE MODE对读取的行记录加一个S锁,其他事务可以向被锁定的行加S锁,但是如果加X锁,则会被阻塞

对于一致性非锁定读,即使读取的行已被执行了SELECT…FOR UPDATE,也是可以进行读取的

一致性锁定读则需要检查被读取的行上有没有互斥的锁,假如有互斥的锁存在就需要等待锁的释放

锁的算法

行锁的3种算法

  • Record Lock:单个行记录上的锁
  • Gap Lock:间隙锁,锁定一个范围,但不包含记录本身
  • Next-Key Lock∶Gap Lock+Record Lock,锁定一个范围,并且锁定记录本身

Record Lock总是会去锁住索引记录,如果InnoDB存储引擎表在建立的时候没有设置任何一个索引,那么这时InnoDB存储引擎会使用隐式的主键来进行锁定

Gap Lock的作用是为了阻止多个事务将记录插入到同一范围内,而这会导致幻读问题的产生

用户可以通过以下两种方式来显式地关闭Gap Lock:

  • 将事务的隔离级别设置为READ COMMITTED
  • 将参数innodb_locks_unsafe_for_binlog设置为1

在上述的配置下,除了外键约束和唯一性检查依然需要的Gap Lock,其余情况仅使用RecordLock进行锁定。

但需要牢记的是,上述设置破坏了事务的隔离性,并且对于replication,可能会导致主从数据的不一致。

Next-Key Lock是结合了Gap Lock和Record Lock的一种锁定算法,在Next-Key Lock算法下,InnoDB对于行的查询都是采用这种锁定算法

当查询的索引含有唯一属性时,InnoDB存储引擎会对Next-Key Lock进行优化,将其降级为Record Lock,即仅锁住索引本身,而不是范围

幻像问题

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

看下面的场景:

  • 表t由1、2、5这三个值组成
  • 执行select * from t where a > 2 for update;
  • 上述事务T1并没有提交,那么此时另一个事务T2插入4这个值,并且数据库允许这个操作
  • 那么事务T1再执行上述查询,就得到4、5两笔记录,跟第一次得到的结果不一样,违反了事务的隔离性

InnoDB存储引擎采用Next-Key Locking的算法避免幻像问题。对于上述的SQL语句select * from t where a > 2 for update,其锁住的不是5这单个值,而是对(2,+∞)这个范围加了X锁。因此任何对于这个范围的插入都是不被允许的,从而避免幻像问题

InnoDB存储引擎默认的事务隔离级别是REPEATABLE READ,在该隔离级别下,其采用Next-Key Locking的方式来加锁

而在事务隔离级别READ COMMITTED下,其仅采用RecordLock行锁

锁的问题

脏读

脏读指的就是在不同的事务下,当前事务可以读到另外事务未提交的数据,简单来说就是可以读到脏数据

脏读发生的条件是需要事务的隔离级别为READ UNCOMMITTED,而目前绝大部分的数据库都至少设置成READCOMMITTED。

InnoDB存储引擎默认的事务隔离级别为READ REPEATABLE,Microsoft SQLServer数据库为READ COMMITTED,Oracle数据库同样也是READ COMMITTED

不可重复读

不可重复读是指在一个事务内多次读取同一数据集合。在这个事务还没有结束时,另外一个事务也访问该同一数据集合,并做了一些DML操作。因此,在第一个事务中的两次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的数据可能是不一样的。这样就发生了在一个事务内两次读到的数据是不一样的情况,这种情况称为不可重复读

不可重复读和脏读的区别是:脏读是读到未提交的数据,而不可重复读读到的却是已经提交的数据,但是其违反了数据库事务一致性的要求

一般来说,不可重复读的问题是可以接受的,因为其读到的是已经提交的数据,本身并不会带来很大的问题。因此,很多数据库厂商(如Oracle、Microsoft SQL Server)将其数据库事务的默认隔离级别设置为READ COMMITTED,在这种隔离级别下允许不可重复读的现象

在InnoDB存储引擎中,通过使用Next-Key Lock算法来避免不可重复读的问题。

在MySQL官方文档中将不可重复读的问题定义为Phantom Problem,即幻像问题。在Next-Key Lock算法下,对于索引的扫描,不仅是锁住扫描到的索引,而且还锁住这些索引覆盖的范围(gap)。

因此在这个范围内的插入都是不允许的。这样就避免了另外的事务在这个范围内插入数据导致的不可重复读的问题。因此,InnoDB存储引擎的默认事务隔离级别是READ REPEATABLE,采用Next-Key Lock算法,避免了不可重复读的现象

丢失更新

丢失更新是另一个锁导致的问题,简单来说其就是一个事务的更新操作会被另一个事务的更新操作所覆盖,从而导致数据的不一致

  • 事务T1将行记录r更新为v1,但是事务T1并未提交
  • 与此同时,事务T2将行记录r更新为v2,事务T2未提交
  • 事务T1提交
  • 事务T2提交

在当前数据库的任何隔离级别下,都不会导致数据库理论意义上的丢失更新问题。这是因为,即使是READ UNCOMMITTED的事务隔离级别,对于行的DML操作,需要对行或其他粗粒度级别的对象加锁

死锁

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

解决死锁问题最简单的一种方法是超时,即当两个事务互相等待时,当一个等待时间超过设置的某一阈值时,其中一个事务进行回滚,另一个等待的事务就能继续进行。在InnoDB存储引擎中,参数innodb_lock_wait_timeout用来设置超时的时间

超时机制虽然简单,但是其仅通过超时后对事务进行回滚的方式来处理,或者说其是根据FIFO的顺序选择回滚对象。但若超时的事务所占权重比较大,如事务操作更新了很多行,占用了较多的undo log,这时采用FIFO的方式,就显得不合适了,因为回滚这个事务的时间相对另一个事务所占用的时间可能会很多

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

较之超时的解决方案,这是一种更为主动的死锁检测方式。InnoDB存储引擎也采用的这种方式。

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

  • 锁的信息链表
  • 事务等待链表

wait-for graph的死锁检测通常采用深度优先的算法实现,通常来说InnoDB存储引擎选择回滚undo量最小的事务

到此这篇关于详解MySql中InnoDB存储引擎中的各种锁的文章就介绍到这了,更多相关详解MySql中InnoDB存储引擎中的各种锁内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

MySQL 相关文章推荐
一篇文章弄懂MySQL查询语句的执行过程
May 07 MySQL
一看就懂的MySQL的聚簇索引及聚簇索引是如何长高的
May 25 MySQL
linux下导入、导出mysql数据库命令的实现方法
May 26 MySQL
Mysql数据库索引面试题(程序员基础技能)
May 31 MySQL
MySQL连表查询分组去重的实现示例
Jul 01 MySQL
低版本Druid连接池+MySQL驱动8.0导致线程阻塞、性能受限
Jul 01 MySQL
MySQL系列之六 用户与授权
Jul 02 MySQL
MySQL8.0升级的踩坑历险记
Nov 01 MySQL
关于mysql中时间日期类型和字符串类型的选择
Nov 27 MySQL
MySQL多表查询机制
Mar 17 MySQL
MySql重置root密码 --skip-grant-tables
Apr 11 MySQL
MySQL优化之慢日志查询
Jun 10 MySQL
Mysql忘记密码解决方法
Mysql存储过程、触发器、事件调度器使用入门指南
Jan 22 #MySQL
MySQL数据库⾼可⽤HA实现小结
weblogic服务建立数据源连接测试更新mysql驱动包的问题及解决方法
Jan 22 #MySQL
解决Mysql多行子查询的使用及空值问题
Jan 22 #MySQL
如何避免mysql启动时错误及sock文件作用分析
Jan 22 #MySQL
教你使用VS Code的MySQL扩展管理数据库的方法
Jan 22 #MySQL
You might like
PHP 第一节 php简介
2012/04/28 PHP
多个PHP中文字符串截取函数
2013/11/12 PHP
PHP文件上传判断file是否己选择上传文件的方法
2014/11/10 PHP
PHP利用func_get_args和func_num_args函数实现函数重载实例
2014/11/12 PHP
ecshop 2.72如何修改后台访问地址
2015/03/03 PHP
PHP提高编程效率的20个要点
2015/09/23 PHP
Centos7 Yum安装PHP7.2流程教程详解
2019/07/02 PHP
php上传后台无法收到数据解决方法
2019/10/28 PHP
PHP7创建销毁session的实例方法
2020/02/03 PHP
客户端 使用XML DOM加载json数据的方法
2010/09/28 Javascript
js Form.elements[i]的使用实例
2011/11/13 Javascript
Angularjs 基础入门
2014/12/26 Javascript
JavaScript数组对象赋值用法实例
2015/08/04 Javascript
JS碰撞运动实现方法详解
2016/12/15 Javascript
JavaScript与Java正则表达式写法的区别介绍
2017/08/15 Javascript
vue 设置路由的登录权限的方法
2018/07/03 Javascript
使用Python判断质数(素数)的简单方法讲解
2016/05/05 Python
Linux 下 Python 实现按任意键退出的实现方法
2016/09/25 Python
Python json 错误xx is not JSON serializable解决办法
2017/03/15 Python
pyhanlp安装介绍和简单应用
2019/02/22 Python
pytorch 获取层权重,对特定层注入hook, 提取中间层输出的方法
2019/08/17 Python
详解Django配置优化方法
2019/11/18 Python
在python image 中实现安装中文字体
2020/05/16 Python
python 发送邮件的示例代码(Python2/3都可以直接使用)
2020/12/03 Python
python实现不同数据库间数据同步功能
2021/02/25 Python
爱尔兰最大的体育零售商:Life Style Sports
2019/06/12 全球购物
汽车维修与检测专业应届生求职信
2013/11/12 职场文书
家长给小学生的评语
2014/01/30 职场文书
大学自主招生自荐信范文
2014/02/26 职场文书
电工实训报告总结
2014/11/05 职场文书
2015年乡镇工作总结范文
2015/04/22 职场文书
无工作证明怎么写
2015/06/15 职场文书
2015年小学生暑假总结
2015/07/13 职场文书
学校运动会加油词
2015/07/18 职场文书
mysql left join快速转inner join的过程
2021/06/30 MySQL
Python 数据可视化之Bokeh详解
2021/11/02 Python