MySQL事务与锁,看这一篇就够了!

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
日志服务 SLS,月写入数据量 50GB 1个月
简介: MySQL事务与锁,看这一篇就够了!

事务与锁

上一篇文章MySQL查询篇,熟悉了连接查询、查询排序等原理,这篇文章来总结事务与锁相关的知识,主要有事务ACID原则、事务实现的redo log、undo log、MVCC、隔离性问题、锁等

事务特性ACID

  • 原子性:一组事务要么都成功要么都失败
  • 隔离性:多用户并发读写操作相同数据时,可能会出现隔离性问题;基本的隔离性需要使用户感知不到其他用户的操作
  • 一致性:数据库中数据满足约束,通常这种约束在业务层面约束
  • 持久性:事务提交成功就永久保存在磁盘上

事务的实现

持久化的实现

redo log

如果在内存中修改完记录,就立马将记录写回磁盘持久化,这样可能会产生随机写IO

为了防止随机写IO开销大的情况,当发生修改等操作时会使用redo log来记录对某页上记录的物理操作,并将redo log持久化到磁盘,等到合适的情况下再将真正的脏页刷入磁盘,由于redo log持久化到磁盘使用顺序写入,因此开销小

在合适的时机刷入磁盘后,redo log记录那些刷入磁盘的操作就没有用了,因此redo log的物理文件被设计为可覆盖的环形文件;redo log由redo log block 管理,redo log buffer由redo log block组成

事务中的每一条语句对应一组MTR(Mini-Transaction),MTR是对底层的原子访问,不同事务的MTR可以被交替的写入buffer中,默认策略下当事务提交时会将redo log刷入磁盘,因为MTR交替写入buffer中,所以可能存在当前事务还未提交但是redo log已经被其他事务刷入磁盘;恢复数据时由bin log引导,就算该事务最后未提交也不会导致这些redo log用来恢复数据时引起数据不一致

image.png

使用升序的lsn日志序列号来记录日志量,lsn为redo log总日志量,flush to disk lsn为刷入磁盘的redo log日志量,check point lsn表示check point指到的日志量;checkpoint lsn表示可以覆盖的redo log位置,在check point lsn前的redo log都可以覆盖,由于redo log是环形文件,如果脏页不刷进磁盘,那么redo log文件可能会满从而导致影响用户线程,check point操作是将脏页和redo log刷到磁盘并更新check point lsn

image.png

double write

如果在此期间数据库宕机,那么下次启动时可以根据已经持久化的redo log对数据进行恢复,需要注意的是redo log是在已有的页上进行物理操作进行恢复,如果页没保存下来就不能使用redo log恢复,因此innodb使用double write两次写来保证页的完整,double write 也是使用顺序IO代替随机IO

doublewrite是将脏页复制到doublewrite buffer中,先将脏页顺序写入磁盘(顺序IO),最后才将脏页写入对应表空间文件(随机IO)

image.png

而redo log刷入磁盘的单位为block以此来保证完整,因此redo log刷盘不需要double write

bin log、redo log两阶段提交

bin log是server层逻辑恢复数据的日志,redo log是innodb层面物理操作页恢复数据的日志

当事务提交时,如果bin log和redo log不一起持久化,那可能会出现数据一致性问题;如果写完redolog断电,那么binlog没记录,下次恢复时,会导致少更新一次(虽然redolog记录了,但是要按照binlog记录的来恢复);如果写完binlog断电,redolog没记录,下次恢复时,会导致无法恢复(按照binlog记录的恢复,但是redoLog没记录到如何恢复)

image.png

  • 两阶段提交过程(默认策略情况下)
  1. redo log prepare write:redo log 从 redo log buffer 写入page cache
  2. bin log write : bin log 从 bin log buffer 写入 page cache
  3. redo log prepare fsync : redo log 从 page cache 刷入磁盘
  4. bin log fsync :bin log 从 page cache 刷入磁盘
  5. redo log commit write

原子性的实现

undo log

在一次事务中,可能进行写操作,如果事务失败就需要回滚,将修改的值变为事务启动前的值

undo log使用回滚段管理,存储在共享表空间,实际类型存储在回滚页上,使用链表来管理不同类型(update、delete、insert等)的undo log页

undo log则是用来记录一个反向操作,记录上的回滚指针指向上次修改的undo log,undo log的回滚指针又指向上次修改的undo log,事务回滚时可以通过undo log回滚达到数据一致性

MVCC

MVCC 多版本并发控制,利用版本链、读快照等控制读数据能够读到版本链上的具体哪条数据,使用MVCC无锁并发控制解决隔离性问题(部分幻读不能解决)

事务id会全局维护自增,如果是查询语句则为0

行记录有隐藏列回滚指针指向上一次修改的undo log,undo log又指向上一次修改的undo log,从而形成版本链;使用读视图 read view 来判断此次读能够读取版本链上的哪条数据

image.png

read view会维护活跃事务id列表、当前最小事务id、当前最大事务id等信息,通过读视图上的事务id与版本链上不同版本的事务id对比来判断是否能读,如果不能读则遍历版本链上的下一个版本判断

  • 判断规则
  • 如果版本上的事务id小于最小事务id则说明事务已提交可以访问
  • 如果版本上的事务id大于最大事务id则说明版本是在读视图生成后执行的,不能访问
  • 如果版本上的事务id在最大、最小事务id之间,则判断事务id是否存在活跃列表中,存在则可以访问否则不能
  • 如果版本上的事务id 等于 当前读视图的事务id 说明是当前事务修改的,可以访问 (如果事务A修改了事务B新增的记录这种情况会出现幻读)
purge

删除记录实际不是立马删除,为了满足MVCC,先标记删除,等到MVCC版本链过期使用不到了再由后台线程purge最终清理

为了避免清理时发生随机读,purge时从链表尾部获取最旧的undo log,再去对应的页清除,顺便将页中其他事务undo log清理

image.png

锁的简介

innodb中的行锁可以类比JUC下的读写锁,X锁排他锁就是写锁,S锁共享锁就是读锁,除了读读的情况其他情况都会阻塞(读写、写写)

innodb中的表锁有意向锁,表示这个表中某记录存在X或S锁;IX表示意向排他锁表中存在X锁,IS表示意向共享锁表中存在S锁

默认情况在并发读写时,不加锁的情况下使用MVCC来读取版本链上的记录

如果使用锁定读的情况下,lock in share mode加S锁、for update加X锁,修改等操作可以看成加X锁;

具体锁的算法不同的隔离级别不同,在读已提交的情况下使用record记录锁,在可重复读的情况下使用next key锁

record记录锁就是锁住某条记录,next key锁则是record记录锁 + gap间隙锁,间隙锁锁住当前记录与上一条记录之间的间隙,不允许插入,以此来解决幻读问题

隔离性问题

  • 可能出现的隔离性问题:脏写、脏读、不可重复读、幻读
  • 脏写:事务A在修改了事务B修改未提交的数据(写写并发太严重了,不允许出现)
  • 脏读:事务A读到了事务B未提交的修改,事务B后续回滚,事务A就会读到脏数据
  • 不可重复读:事务B修改了事务A读过的数据,事务A在同次事务中再读,发现两次读取数据不一致
  • 幻读:事务B新增数据,事务A在事务B操作前后读取数据,最后的读取发现数据增加
  • 不同隔离级别(按顺序隔离级别限制越严格性能就越不好)
  • 读未提交:允许读到其他事务未提交的数据,在此隔离级别下可能发生:脏读、不可重复读、幻读
  • 读已提交:只允许读到其他事务已提交的数据,在此隔离级别下可能发生:不可重复读、幻读;读时使用MVCC,写时使用record锁
  • 可重复读:innodb默认隔离级别,可能发生特殊幻读;读时使用MVCC,写时使用next key
  • 串行化:读写加S/X锁串行化,避免隔离性问题

读已提交隔离级别情况下,读时使用MVCC,每次读产生读快照,以此来读到已提交的记录

可重复读隔离级别情况下,只在事务的第一次读产生快照,因此事务中每次读都是第一次读能够看到记录版本,以此来避免幻读,注意如果事务中修改了其他事务新增的记录会导致该快照对这些数据可见,从而导致幻读发生

加锁规则

使用哪些索引,才对哪些索引加锁;读已提交使用record锁,在遍历完索引后对不需要的记录释放锁;可重复读使用next key锁,在遍历完索引后不对不需要的记录释放锁(防止新插入),在事务提交或回滚才释放

增删改操作当成锁定读,加X锁处理;X锁只使用二级索引时,会把聚簇索引对应记录也加锁,而S锁不会

原则 1:加锁的基本单位是 next-key lock。希望你还记得,next-key lock 是前开后闭区间。

原则 2:查找过程中访问到的对象才会加锁。

优化 1:索引上的等值查询,给唯一索引加锁的时候,next-key lock 退化为行锁。

优化 2:索引上的等值查询,向右遍历时且最后一个值不满足等值条件的时候,next-key lock 退化为间隙锁。

一个 bug:唯一索引上的范围查询会访问到不满足条件的第一个值为止。

死锁

当多个线程抢占同一行记录时可能发生死锁(资源互斥、占有资源、不释放资源、成环),innodb使用wait for graph来判断是否死锁(事务需要等待其他事务时就指向其他事务,当构成的图成环时则发生死锁),检测死锁有性能开销

image.png

排查方案:

  1. show engine innodb status 查看最近一次死锁
  2. 联表查询information_schema库中的innodb_trx、innodb_locks、innodb_lock_waits表等查看相关SQL、事务、锁、等待时间等信息
  3. 再去业务层排查代码写法问题,更改顺序等手段阻止死锁发生

总结

innodb 中使用 double write 保证页的完整(redo log基于block为单位刷入磁盘,因此不需要double write),使用redo log 记录页中物理的操作(顺序写取代持久化的随机写)

在发生宕机可以通过bin log与redo log恢复数据(redo log完整恢复,不完整判断是否有bin log),并且bin log与redo log需要两阶段提交来满足数据一致性

因为脏页刷入磁盘后redo log就没有作用了,所以redo log设计为环形可覆盖文件,如果脏页不刷盘可能导致redo log占满,这可能导致用户线程主动触发脏页刷盘从而影响用户线程

innodb 中使用undo log记录回滚操作,行记录中的隐藏列回滚指针指向上一次修改的undo log,而undo log的回滚指针又指向上次修改的undo log,从而形成版本链,事务失败需要回滚时可以通过undo log回滚到某个版本

MVCC通过版本链 + read view 读视图的方式实现无锁的并发读,read view根据生成时的事务id和维护的事务id来判断当前事务是否能够读到版本链上的记录,不能则判断版本链上的下一条

读已提交隔离级别下每次读生成read view因此能够看到已经提交事务的数据,可重复读隔离级别只在第一次读生成read view因此多次读到的数据都是相同的

MVCC 能够避免大部分幻读(特殊情况下,事务B新增数据,事务A修改了新增数据导致read view对数据可见从而产生幻读),而可重复读级别下使用next key算法防止幻读

非锁定读的情况下能够使用mvcc解决隔离性问题,锁定读的情况下使用S/X锁来解决隔离性问题;读已提交级别下使用record lock,可重复读级别下使用next key锁,使用到哪些索引就对哪些索引加锁,因此加锁不使用索引时,会给聚簇索引加锁

当满足一定条件时会发生死锁,死锁检测使用wait for groph依赖图成环就死锁,可以查询死锁相关信息,排查业务层分析代码写法问题,破坏死锁生成条件,阻止死锁发生


相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助     相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
18天前
|
SQL 关系型数据库 MySQL
MySQL 锁
MySQL里常见的几种锁
43 3
|
8天前
|
存储 SQL 关系型数据库
MySQL的事务隔离级别
【10月更文挑战第17天】MySQL的事务隔离级别
83 43
|
21天前
|
存储 缓存 关系型数据库
MySQL事务日志-Redo Log工作原理分析
事务的隔离性和原子性分别通过锁和事务日志实现,而持久性则依赖于事务日志中的`Redo Log`。在MySQL中,`Redo Log`确保已提交事务的数据能持久保存,即使系统崩溃也能通过重做日志恢复数据。其工作原理是记录数据在内存中的更改,待事务提交时写入磁盘。此外,`Redo Log`采用简单的物理日志格式和高效的顺序IO,确保快速提交。通过不同的落盘策略,可在性能和安全性之间做出权衡。
1585 14
|
9天前
|
存储 关系型数据库 MySQL
优化 MySQL 的锁机制以提高并发性能
【10月更文挑战第16天】优化 MySQL 锁机制需要综合考虑多个因素,根据具体的应用场景和需求进行针对性的调整。通过不断地优化和改进,可以提高数据库的并发性能,提升系统的整体效率。
15 1
|
13天前
|
SQL 关系型数据库 MySQL
阿里面试:MYSQL 事务ACID,底层原理是什么? 具体是如何实现的?
尼恩,一位40岁的资深架构师,通过其丰富的经验和深厚的技術功底,为众多读者提供了宝贵的面试指导和技术分享。在他的读者交流群中,许多小伙伴获得了来自一线互联网企业的面试机会,并成功应对了诸如事务ACID特性实现、MVCC等相关面试题。尼恩特别整理了这些常见面试题的系统化解答,形成了《MVCC 学习圣经:一次穿透MYSQL MVCC》PDF文档,旨在帮助大家在面试中展示出扎实的技术功底,提高面试成功率。此外,他还编写了《尼恩Java面试宝典》等资料,涵盖了大量面试题和答案,帮助读者全面提升技术面试的表现。这些资料不仅内容详实,而且持续更新,是求职者备战技术面试的宝贵资源。
阿里面试:MYSQL 事务ACID,底层原理是什么? 具体是如何实现的?
|
2月前
|
存储 Oracle 关系型数据库
Oracle和MySQL有哪些区别?从基本特性、技术选型、字段类型、事务、语句等角度详细对比Oracle和MySQL
从基本特性、技术选型、字段类型、事务提交方式、SQL语句、分页方法等方面对比Oracle和MySQL的区别。
317 18
Oracle和MySQL有哪些区别?从基本特性、技术选型、字段类型、事务、语句等角度详细对比Oracle和MySQL
|
24天前
|
关系型数据库 MySQL Java
MySQL数据锁:Record Lock,Gap Lock 和 Next-Key Lock
本文基于 MySQL 8.0.30 版本及 InnoDB 引擎,深入解析三种行锁机制:记录锁(Record Lock)、间隙锁(Gap Lock)和临键锁(Next-key Lock)。记录锁锁定索引记录,确保事务唯一修改;间隙锁锁定索引间的间隙,防止新记录插入;临键锁结合两者,锁定范围并记录自身,有效避免幻读现象。通过具体示例展示了不同锁的作用机制及其在并发控制中的应用。
52 2
|
24天前
|
存储 关系型数据库 MySQL
MySQL数据库锁:共享锁和独占锁
本文详细介绍了`InnoDB`存储引擎中的两种行级别锁:共享锁(S锁)与排他锁(X锁)。通过具体示例展示了这两种锁的工作机制及其在`InnoDB`与`MyISAM`引擎中的表现差异。文章还提供了锁的兼容性矩阵,帮助读者更好地理解锁之间的互斥关系。最后总结了两种锁的特点及适用场景。适合希望深入了解`MySQL`并发控制机制的读者阅读。
11 1
|
9天前
|
存储 关系型数据库 MySQL
MySQL锁,锁的到底是什么?
【10月更文挑战第16天】MySQL 锁锁定的是与数据和资源相关的对象,其目的是为了保证数据的一致性、避免冲突,并在并发环境下合理协调事务或操作的执行。理解锁的对象和意义对于优化数据库性能、处理并发问题至关重要。
36 0
|
2月前
|
监控 关系型数据库 MySQL
MySQL锁机制与解决死锁问题
MySQL锁机制与解决死锁问题
180 5