MySQL事务

事务的定义

事务是作为单个逻辑工作单元执行的一系列操作,通俗易懂的说就是一组原子性的 SQL 查询。Mysql 中事务的支持在存储引擎层,MyISAM 存储引擎不支持事务,而 InnoDB 支持,这是 Mysql 5.5.5 以后默认引擎由 MyISAM 换成 InnoDB 的最根本原因。

事务的 ACID 属性

  • 原子性(Atomicity):作为逻辑工作单元,一个事务里的所有操作的执行,要么全部成功,要么全部失败。
  • 一致性(Consistency):数据库从一个一致性状态变换到另外一个一致性状态,数据库的完整性不会受到破坏。
  • 隔离性(Isolation):通常来说,一个事务所做的修改在最终提交前,对其他事务是不可见的。为什么是通常来说,为了提高事务的并发引出不同的隔离级别,具体参考下一章节。
  • 持久性(Durability):一旦事务提交,则其所做的修改就会永久保存到数据库中,即使系统故障,修改的数据也不会丢失。

事务的隔离级别

为了尽可能的高并发,事务的隔离性被分为四个级别:读未提交、读已提交、可重复读和串行化。用户可以根据需要选择不同的级别。

未提交读(READ UNCOMMITTED):一个事务还未提交,它的变更就能被别的事务看到。

:事务 A 可以读到事务 B 修改的但还未提交的数据,会导致脏读(可能事务 B 在提交后失败了,事务 A 读到的数据是脏的)。

提交读(READ COMMITTED):一个事务提交后,它的变更才能被其他事务看到。大多数据库系统的默认级别,但 Mysql 不是。

:事务 A 只能读到事务 B 修改并提交后的数据,会导致不可重复读(事务 A 中执行两次查询,一次在事务 B 提交过程中,一次在事务 B 提交之后,会导致两次读取的结果不一致)。

可重复读(REPEATABLE READ):未提交的事务的变更不能被其他事务看到,同时一次事务过程中多次读取同样记录的结果是一致的。
:事务 A 在执行过程中多次获取某范围内的记录,事务 B 提交后在此范围内插入或者删除 N条记录,事务 A 执行过程中多次范围读会存在不一致,即幻读(Mysql 的默认级别,InnoDB 通过 MVVC 解决了幻读的问题)。

可串行化(SERIALIZABLE):当两个事务间存在读写冲突时,数据库通过加锁强制事务串行执行,解决了前面所说的所有问题(脏读、不可重复读、幻读)。是最高隔离的隔离级别。

事务的MVCC机制

MVCC允许多个事务同时读取同一行数据,而不会彼此阻塞,每个事务看到的数据版本是该事务开始时的数据版本。这意味着,如果其他事务在此期间修改了数据,正在运行的事务仍然看到的是它开始时的数据状态,从而实现了非阻塞读操作。

对于「读提交」和「可重复读」隔离级别的事务来说,它们是通过 Read View 来实现的,它们的区别在于创建 Read View 的时机不同,大家可以把 Read View 理解成一个数据快照,就像相机拍照那样,定格某一时刻的风景。

  • 「读提交」隔离级别是在「每个select语句执行前」都会重新生成一个 Read View;
  • 「可重复读」隔离级别是执行第一条select时,生成一个 Read View,然后整个事务期间都在用这个 Read View。

Read View 有四个重要的字段:

  • m_ids :指的是在创建 Read View 时,当前数据库中「活跃事务」的事务 id 列表,注意是一个列表,“活跃事务”指的就是,启动了但还没提交的事务
  • min_trx_id :指的是在创建 Read View 时,当前数据库中「活跃事务」中事务 id 最小的事务,也就是 m_ids 的最小值。
  • max_trx_id :这个并不是 m_ids 的最大值,而是创建 Read View 时当前数据库中应该给下一个事务的 id 值,也就是全局事务中最大的事务 id 值 + 1;
  • creator_trx_id :指的是创建该 Read View 的事务的事务 id

对于使用 InnoDB 存储引擎的数据库表,它的聚簇索引记录中都包含下面两个隐藏列:

  • trx_id,当一个事务对某条聚簇索引记录进行改动时,就会把该事务的事务 id 记录在 trx_id 隐藏列里
  • roll_pointer,每次对某条聚簇索引记录进行改动时,都会把旧版本的记录写入到 undo 日志中,然后这个隐藏列是个指针,指向每一个旧版本记录,于是就可以通过它找到修改前的记录。

在创建 Read View 后,我们可以将记录中的 trx_id 划分这三种情况:

一个事务去访问记录的时候,除了自己的更新记录总是可见之外,还有这几种情况:

  • 如果记录的 trx_id 值小于 Read View 中的 min_trx_id 值,表示这个版本的记录是在创建 Read View 已经提交的事务生成的,所以该版本的记录对当前事务可见
  • 如果记录的 trx_id 值大于等于 Read View 中的 max_trx_id 值,表示这个版本的记录是在创建 Read View 才启动的事务生成的,所以该版本的记录对当前事务不可见
  • 如果记录的 trx_id 值在 Read View 的 min_trx_id 和 max_trx_id 之间,需要判断 trx_id 是否在 m_ids 列表中:
    • 如果记录的 trx_id  m_ids 列表中,表示生成该版本记录的活跃事务依然活跃着(还没提交事务),所以该版本的记录对当前事务不可见
    • 如果记录的 trx_id 不在 m_ids列表中,表示生成该版本记录的活跃事务已经被提交,所以该版本的记录对当前事务可见

这种通过「版本链」来控制并发事务访问同一个记录时的行为就叫 MVCC(多版本并发控制)。

事务的实现

事务的隔离性通过锁或 MVCC 机制来实现,而原子性、持久性和一致性通过 redo/undo log 来完成。redo log 称为重做日志,用来保证事务的原子性和持久性。undo log 称为撤销日志,用来保证事务的一致性。

redo log

基本概念

重做日志用来实现事务的持久性,由以下两部分组成:

  • 重做日志缓冲区(redo log buffer),内存中,易丢失。
  • 重做日志文件(redo log file),磁盘中,持久的。

redo log file 是顺序写入的,在数据库运行时不需要进行读取,只会在数据库启动的时候读取来进行数据的恢复工作。
redo log file 是物理日志,所谓的物理日志是指日志中的内容都是直接操作物理页的命令。重做时是对某个物理页进行相应的操作。

整体流程

更新事务操作一次数据的流程图如下所示:

  • 第一步:先将原始数据从磁盘中读入内存中来,修改数据的内存拷贝。
  • 第二步:生成一条重做日志并写入redo log buffer,记录的是数据被修改后的值。
  • 第三步:在必要的时候,采用追加写的方式将 redo log buffer 中的内容刷新到 redo log file。
  • 第四步:定期将内存中修改的数据刷新到磁盘中。

以上比较重要的是第三步,其中必要的时候有以下几种情况:
- 事务提交时(最常见的情景,在 commit 之前)
- 当 log buffer 中有一半的内存空间被使用时
- log checkpoint 时
- 实例 shutdown 时
- binlog切换时
- 后台线程

 写入策略

学过 linux 操作系统的都知道内存中数据写入到磁盘文件中时如果不打开 O_DIRECT 选项。数据是要先写入文件操作系统缓存区中的,然后再某个时刻 flush 到磁盘。流程如下:

事务提交时将 redo log buffer 写入 redo log file,为了保证数据一定能正确同步到磁盘(不仅仅只写到文件缓冲区中)文件中,InndoDB 默认情况下调用了 fsync 进行写操作,而 fsync 的性能比较低。 

恢复

InnoDB 引擎启动时不管上次数据库运行时是否正常关闭,都会尝试进行恢复操作。整个恢复操作有如下特点:

  • 从 checkpoint 开始的日志部分进行恢复。
  • 顺序读取及并行应用重做日志。
  • 重做日志的应用具有幂等性
  • 重做日志是物理日志,恢复的速度相对较快。

undo log

基本概念

  • undo log 用来实现事务的一致性,InndoDB 可以通过 redo log 对页进行重做操作。但是有时候事务需要进行回滚,这时就需要 undo log。
  • undo log 还可可以用来协助 InnoDB 引擎实现 MVCC 机制。
  • undo log 是逻辑日志,恢复时并不是对物理页直接进行恢复,而是逻辑地将数据库恢复到原来的样子。
  • undo log 的产生也会伴随着 redo log 的产生。

写入时机

事务开始之前,流程如图:

redo 与 undo log 记录过程

undo 记录更新之前的日志,为了回滚。
redo 记录更新之后的日志,为了重做。
redo log 与 undo log 产生过程的简化版本如下,可以更方便的理解 redo 与 undo 的区别。

假设有A,B两个数据,原值分别为 1, 2;现将A更新为10,B 更新为 20;undo log记录信息过程如下:
1. 事务开始
2. 记录 A = 1 到 undo log
3. 更新 A = 10
4. 记录 A = 10 到 redo log
5. 记录 B = 2 到 undo log
6. 更新 B = 20
7. 记录 B = 20 到 redo log
8. redo log 信息写入磁盘
9. 提交事务


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

**K

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值