网站首页 > 技术文章 正文
锁是数据库系统与文件系统区别的一个关键特性。锁是用于管理对共享资源的并发访问,对于锁,不同的数据库有不同的实现方式。在InnoDB存储引擎中有非常多的锁设计,其设计思路与Oracle有诸多的相似之处,比如提供了一致性的非锁定读,行级别锁等。
锁相关数据结构
在InnoDB存储引擎中,锁是为事务服务的,涉及到的锁相关的表有innodb_trx、data_locks、data_locks_waits。
- innodb_trx:记录当前正在执行的事务信息(包括只读事务)。
- data_locks表:记录所有事务未释放的锁信息(如果事务发生严重的锁等待可以通过查询该表定位问题)。
- data_locks_waits表:记录data_locks中锁与锁之间等待及依赖关系表,同时也记录了所对应的事务信息。
我们通过一个示例来看一下三者之间的关系:
查询了user表的user_id(该列建立了唯一索引)
BEGIN;
SELECT * FROM user where user_id = "18" for update;
SELECT * FROM performance_schema.data_locks;
user_id的查询在表data_locks中会产生三条数据,第一条为表级别意向排他锁(IX),第二条与第三条都为行记录排他锁(X)(注:REC_NOT_GAP为排它非间隙锁),他们的区别是LOCK_DATA的不同,LOCK_DATA的值通常表示的是主键ID,如果我们使用到辅助索引查询时,那么锁住的范围会包含聚簇索引+辅助索引,此时的LOCK_DATA则为辅助索引字段值+主键ID(见上图圈红部分)。
如果我们开启三个事务,下图为三个事务存储在innodb_trx、data_locks、locks_waits三张表中的数据关系:
InnoDB存储引擎中的锁
MySQL的InnoDB存储引擎支持行锁以及表锁,在讲解锁实现前我们先要明确一个概念,本章讲解的锁区别于我们平常开发过程中经常使用到的临界资源锁(Latch,例如Java中的AQS、synchronized等),本章讲解的是MySQL作用于事务的锁,用于锁定表、页、行等粗粒度的资源。
行锁
InnoDB引擎实现了两种标准的行级锁,分别是共享行级锁、排它行级锁。
- 共享行级锁:又称S锁、Share Lock、读锁。
如语句:select ... lock in share mode等会加共享行锁。
- 排它行级锁:又称X锁、Exclusive Lock、写锁。
如语句:update、delete、insert、select ... for update等会加排它行锁。
S锁与X锁的兼容性如下:
S锁 | X锁 | |
S锁 | 兼容 | 不兼容 |
X锁 | 不兼容 | 不兼容 |
行锁的算法实现
InnoDB存储引擎存在三种行锁算法,分别为Record Lock、Gap_Lock、Next-Key Lock。
行锁(Record Lock)
Record Lock表示的是单行记录上的锁,例如文章起始的示例,执行for update时事务会为每一条数据加上X锁,如果使用到辅助索引,则会锁定聚簇索引记录与辅助索引记录。如下图data_locks表的LOCK_TYPE为RECORD,LOCK_MODE为X,REC_NOT_GAP(REC_NOT_GAP表示为行锁,非间隙锁),LOCK_DATA为:'18',18,表示锁住的是聚簇索引记录与辅助索引记录。
间隙锁(Gap Lock)
间隙锁顾名思义也就是锁住一个键值范围,其存在主要是为了解决幻读的问题。
幻读:当前事务读取一定范围内的数据时,其它事务在该范围内插入或者删除了记录,导致当前事务读取到数据发生了变更,如图产生幻觉一样。
间隙锁会为区间内的所有行加上X锁,如下图data_locks表的LOCK_TYPE为RECORD,LOCK_MODE为X,如果没有指定闭区间LOCK_DATA则为supremum pseudo-record。
临键锁(Next-Key Lock)
Next-Key Lock是Record Lock与Gap Lock的组合,其加锁原则如下(RR隔离级别下):
- 加锁的对象是一个前开后闭的区间;
- 查找过程中访问到的对象才加锁;
- 索引上的等值查询:命中唯一索引时退化为行锁;命中普通索引时,左右两边加Gap Lock+Record Lock;
- 索引上的等值查询,向右遍历时最后一个不满足等值条件的时候,Next-Key Lock 退化为间隙锁;
- 索引范围查询:
1)等值与范围分开判断;
2)索引在范围查询的时候都会访问到所在区间不满足条件的第一个值为止;
3)如果使用到了倒序排序,按照倒序排序后:检索范围的右边多加一个Gap 区间;如果左右两边再命中了等值条件,则需要再向同方向拓展一个外开里闭的区间。
以上原则较为抽象,我们通过一张图来做进一步的解释:
表锁
InnoDB还支持一种锁,其为表级锁,为了支持这两种不同粒度的锁,InnoDB支持一种额外的锁模式,称之为意向锁(Intention Lock)。
意向锁同样分为两种: 共享和排他
- 意向共享锁(IS, Intention Share Lock)
- 意向排他锁 (IX, Intention Exclusive Lock)
意向锁(Intention Lock)作为一种表级锁存在,是为了解决不同事务之间的锁冲突问题,其思路是要想获得行锁,那么必须要获取比行更大粒度的锁,我们来看一个锁的分层图:
意向锁之间的兼容性:
意向排它锁(IX) | 意向共享锁(IS) | |
意向排它锁(IX) | 兼容 | 兼容 |
意向共享锁(IS) | 兼容 | 兼容 |
意向锁不会与行级的共享/排他锁互斥!正因为如此,意向锁并不会影响到多个事务对不同数据行加排他锁时的并发性。
自增锁(AUTO_INC Locking)
最初的自增长锁采用的是特殊的表锁实现,称其为AUTO_INC Locking,为了提高插入的性能,该锁不是在事务执行完成时候释放,而是在自增长值插入成功后立即释放,但是这种实现在高并发下仍然效率不够高。因此自增长锁就有了轻量级的 Mutex(轻量锁)实现,当然这种实现是在高并发时才会启用,没有事务竞争时仍然是AUTO_INC Locking。
元数据锁(Metadata Lock)
元数据锁Metadata Lock,又称为MDL,它与行锁和表锁的区别仅仅是作用对象范围的不同,它的作用范围更广,包含了数据库、表、行、触发器以及外键等。
通常情况下,当我们修改表结构的时候才会出现MDL,如执行ALTER TABLE xxx ADD column 语句时。这个锁会阻塞整张表的所有后续事务,如果存在长事务或者表数据的修改非常频繁,很有可能会导致MySQL进程崩溃。我们来看一个在线的DDL过程,如下:
DDL(Data Definition Language):数据库结构相关的操作语言,关键字:create、alter、drop等。
DML(Data Manipulation Language):数据库数据的相关操作语言,关键字:select、update、delete、insert等。
- ALTER TABLE xxx ADD column语句获取MDL写锁;
- 获取成功后,将其降级为MDL读锁;
- 在执行真正的DDL操作之前,是可以执行DML语句的;
- 升级MDL读锁为写锁,此时所有DML语句会被阻塞;
- 执行完成,释放MDL写锁,DDL执行完成,阻塞的DML语句可以继续执行;
在这个过程中,在没有真正开始执行DDL语句前只是加了MDL读锁,DML语句是可以正常执行的,这也就降低了其它事务的阻塞时间。
插入意向锁(Insert Intention Lock)
插入意向锁是一种间隙锁形式的意向锁,其属于一种行锁,在插入语句发生等待时设置。如下图:
在执行插入操作前,事务A对要插入数据的间隙加了间隙锁,事务B提交了插入语句会进行等待,此时data_locks表中会插入一条如上图所示的数据,LOCK_MODE为X,GAP,INSERT_INTENTION。
锁相关问题
死锁
死锁是指两个及以上的事务在执行的过程中,因争夺锁资源而造成的一种相互等待的现象。
死锁解决方案
- 事务超时机制:解决死锁的最简单方法是超时机制,通过为事务设置等待超时时间来断开依赖链,如果事务超时,则回滚事务。回滚事务的选择也是非常重要的,我们可以通过FIFO队列,按顺序回滚,但如果回滚事务所占用的资源非常多(更新了很多行,写入了很多undo log),那么采用FIFO就不合适了,这是可以按照事务权重会滚。
- wait-for graph(等待图)死锁检查:这种方式是目前数据库普遍采用的方式,这是一种主动监测死锁的机制。在事务请求锁而发生等待时,我们可以通过锁相关的表(innodb_trx、data_locks、data_locks_waits)构建一张图,通过检测图是否有回路来判断是否存在死锁。
锁升级
锁升级(Lock Rscalation)是指将当前锁的粒度降低。例如可以把一张表的N个行锁升级为页锁,或者升级为表锁。锁升级的场景:
- InnoDB存储引擎的行锁锁住的是索引记录,如果没有命中索引,那么只能全表加间隙锁,这等同于表锁;
- 如果命中了索引,但是索引的选择性低(关于选择性的概念可以阅读我的上一篇文章),那么也会升级为全表加间隙锁;
总结
- InnoDB的行锁是针对索引记录加锁,如果没有命中索引,则会给全表的聚簇索引加间隙锁。
- 因为加锁对象是索引,所以可能出现两个事务访问的不同行记录,但是使用到了相同的索引键,也就是有索引键冲突,那么另外一个事务仍然会阻塞。
- 如果条件没有索引或者索引的选择性很低,那么会造成锁升级,也就是全表加间隙锁。
《MySQL系列专栏》持续更新中,关注我不迷路[送心]。
- 上一篇: MySQL中InnoDB引擎的行锁是怎么实现的?
- 下一篇: 数据库锁机制中的行锁和表锁:如何使用?
猜你喜欢
- 2024-11-14 深入浅出MySQL之MySQL锁概述 mysql锁的实现原理
- 2024-11-14 了解SQL编程锁的概念 了解sql编程锁的概念是什么
- 2024-11-14 面试必问的Mysql事务和锁,你真的了解吗?
- 2024-11-14 数据库常用的锁有哪些? 数据库锁种类
- 2024-11-14 MySQL事务和锁的使用 mysql事务锁机制
- 2024-11-14 数据库融入DevOps基因后,运维再也不用做背锅侠了
- 2024-11-14 Mysql锁-01锁相关的一些概念 mysql锁的实现原理
- 2024-11-14 SQL锁是数据库管理系统中用来管理并发访问的一种机制
- 2024-11-14 干货总结:彻底搞懂MySQL数据库锁机制(上篇)
- 2024-11-14 「每天一道面试题」MySQL锁 mysql锁使用
你 发表评论:
欢迎- 613℃几个Oracle空值处理函数 oracle处理null值的函数
- 604℃Oracle分析函数之Lag和Lead()使用
- 593℃0497-如何将Kerberos的CDH6.1从Oracle JDK 1.8迁移至OpenJDK 1.8
- 590℃Oracle数据库的单、多行函数 oracle执行多个sql语句
- 584℃Oracle 12c PDB迁移(一) oracle迁移到oceanbase
- 578℃【数据统计分析】详解Oracle分组函数之CUBE
- 567℃最佳实践 | 提效 47 倍,制造业生产 Oracle 迁移替换
- 559℃Oracle有哪些常见的函数? oracle中常用的函数
- 最近发表
-
- PageHelper - 最方便的 MyBatis 分页插件
- 面试二:pagehelper是怎么实现分页的,
- MyBatis如何实现分页查询?(mybatis-plus分页查询)
- SpringBoot 各种分页查询方式详解(全网最全)
- 如何在Linux上运行exe文件,怎么用linux运行windows软件
- 快速了解hive(快速了解美国50个州)
- Python 中的 pyodbc 库(pydbclib)
- Linux搭建Weblogic集群(linux weblogic部署项目步骤)
- 「DM专栏」DMDSC共享集群之部署(一)——共享存储配置
- 故障分析 | MySQL 派生表优化(mysql pipe)
- 标签列表
-
- 前端设计模式 (75)
- 前端性能优化 (51)
- 前端模板 (66)
- 前端跨域 (52)
- 前端缓存 (63)
- 前端aes加密 (58)
- 前端脚手架 (56)
- 前端md5加密 (54)
- 前端路由 (61)
- 前端数组 (73)
- 前端js面试题 (50)
- 前端定时器 (59)
- 前端获取当前时间 (50)
- Oracle RAC (76)
- oracle恢复 (77)
- oracle 删除表 (52)
- oracle 用户名 (80)
- oracle 工具 (55)
- oracle 内存 (55)
- oracle 导出表 (62)
- oracle约束 (54)
- oracle 中文 (51)
- oracle链接 (54)
- oracle的函数 (58)
- 前端调试 (52)
本文暂时没有评论,来添加一个吧(●'◡'●)