快捷搜索:  汽车  科技

mysql支持undo操作吗?Undo日志到MVCC实现原理

mysql支持undo操作吗?Undo日志到MVCC实现原理存在问题对于insert操作,undo 日志记录新数据的 PK(ROW_ID),回滚时直接删除对于delete/update操作,undo 日志记录旧数据,回滚时直接恢复现需要将 id=1 的 name 修改为 kawaii,流程如下:1、事务开始2、将 id=1 的行数据记录到 undo log buffer3、修改 id=1 的行数据 name 为 kawaii4、undo log buffer 写入磁盘为 undo log file5、将数据写入磁盘6、事务提交步骤 1 - 3 都是在内存中完成,第 4 步之前包括第 4 步如果出现问题,undo log 与数据都未写入磁盘,可以直接回滚,第 4 步之后出现问题,此时 undo log 已写入磁盘,利用 undo log 进行回滚。

文章大纲
  • Undo日志
  • Redo日志
  • MVCC

mysql支持undo操作吗?Undo日志到MVCC实现原理(1)

Undo 日志

关系型数据需要实现事务的 ACID 特性,其中一点就是事务的原子性,Mysql 就是通过 Undo 日志就来实现的。

数据库处理数据都是先读到内存中,然后修改内存中的数据,最后将数据写回磁盘。

在事务处理过程中,操作数据之前会先将数据缓存至 Undo 日志,然后进行数据的修改。当事务回滚时,或者数据库奔溃时,系统可以利用 Undo 日志中的备份将数据恢复到事务开始之前的状态,撤销未提交事务对数据库产生的影响。

具体流程

假设有个用户表,字段有 id、name。

mysql支持undo操作吗?Undo日志到MVCC实现原理(2)

现需要将 id=1 的 name 修改为 kawaii,流程如下:

1、事务开始
2、将 id=1 的行数据记录到 undo log buffer
3、修改 id=1 的行数据 name 为 kawaii
4、undo log buffer 写入磁盘为 undo log file
5、将数据写入磁盘
6、事务提交

步骤 1 - 3 都是在内存中完成,第 4 步之前包括第 4 步如果出现问题,undo log 与数据都未写入磁盘,可以直接回滚,第 4 步之后出现问题,此时 undo log 已写入磁盘,利用 undo log 进行回滚。

对于insert操作,undo 日志记录新数据的 PK(ROW_ID),回滚时直接删除
对于delete/update操作,undo 日志记录旧数据,回滚时直接恢复


存在问题

事务提交时需要将内存中的数据同步写入磁盘,数据写入磁盘属于随机IO,性能较差,会极大影响数据库的吞吐量。

优化方案:将修改行为先写到 Redo 日志(顺序写),再定期将数据刷到磁盘上,这样能极大提高性能。

Redo 日志

Undo 日志存储的是历史数据快照,Redo 日志则存储最新数据。

具体流程

还是以上文中的操作为例:

1、事务开始
2、将 id=1 的行数据记录到 undo log buffer
3、修改 id=1 的行数据 name 为 kawaii
4、记录修改日志到 redo log buffer
5、undo log buffer 写入磁盘为 undo log file
6、redo log buffer 写入磁盘为 redo log file
7、事务提交

都是写磁盘,写 Redo 日志文件与直接写数据库文件有什么区别?

写 Redo 日志文件是顺序IO,而写数据库文件是随机IO,性能差别大。

Redo 日志文件写入成功后,数据库会另起线程将 Redo 日志写入数据库数据文件,实现持久化。

mysql支持undo操作吗?Undo日志到MVCC实现原理(3)

MVCC

MVCC(Multi-Version Concurrency Control)即多版本并发控制,Mysql、Oracle、PostgreSQL等数据库都实现了MVCC,但各自实现的机制不尽相同,因为MVCC没有统一的实现标准。

事务隔离级别

事务有四个隔离级别

mysql支持undo操作吗?Undo日志到MVCC实现原理(4)

  • 脏读:事务中的未提交的修改对其它事务是可见的。

如下表所示,事务B可以读取到事务A未提交的数据。

mysql支持undo操作吗?Undo日志到MVCC实现原理(5)

  • 重复读:事务中多次读取同一数据,结果不一致。

如下表所示,事务B两次读取之间事务A修改了数据并提交事务,事务B的第二次读取与第一次读取结果有变化。

mysql支持undo操作吗?Undo日志到MVCC实现原理(6)

  • 幻读:一个事务在读取某个范围内的记录时,另一个事务在该范围插入的新记录,之前事务再次读取该范围记录时,会产生幻行。

如下表所示,事务B两次读取之间事务A插入了一条数据并提交事务,事务B第二次读取 id < 5 的范围数据时,数量增加了。

mysql支持undo操作吗?Undo日志到MVCC实现原理(7)

什么是MVCC

InnoDB 存储引擎默认的事务隔离级别为 RR (可重复读)

那么 InnoDB 如何实现可重复读?加行级锁是肯定可以实现的,但如果对读操作也加行级锁,将严重影响数据库的并发性能,而MVCC 就是这个问题的解决方案。

MVCC 是一种用来解决读-写冲突的无锁并发控制,通过对行数据的多版本控制,避免了读操作时的加锁操作,因此开销更低,大大提高数据库系统的并发性能。

InnoDB 就是通过 行级锁 MVCC 共同实现事务隔离,正常读的时候不加锁,写的时候对数据行加排它锁。

MVCC 只能在 Read Committed 和 Repeatable Read 两个隔离级别下工作。

MVCC实现原理存储结构

InnoDB 支持聚簇索引,默认设置主键列为聚簇索引,如果表中无主键,则会选择一个唯一的非空索引作为聚簇索引,如果也没有,则会隐式定义一个主键作为聚簇索引。

InnoDB 的数据行存储在聚簇索引上,结构如下:

mysql支持undo操作吗?Undo日志到MVCC实现原理(8)

可以看到索引上不仅存储了索引列、行数据,还包含了两个隐藏字段:

  • DB_TRX_ID(6字节):表示最近一次修改(insert | update)的事务ID。
  • DB_ROLL_PTR(7字节):回滚指针,指向这条记录的上一个版本(存储于 roolback segment 里的 update undo log)

还有一个删除 flag 隐藏字段,delete 操作被认为是一个 update 操作,只是修改了该删除标记位,而不是物理删除。

以上文中的表结构为例:

第一步:事务1,插入一条记录

mysql支持undo操作吗?Undo日志到MVCC实现原理(9)

第二步:事务2,修改 name 为 kawaii,流程如下

1、事务开始,对数据行加排它锁
2、将该行数据记录到 undo log,事务ID 为 1 的数据行
3、修改该行数据 name 为 kawaii,DB_TRX_ID 修改为 2,DB_ROLL_PTR 指向 步骤 2 中记录的 undo log
4、提交事务,释放排它锁

当前最新数据如下,其中 0x23636355 指向 undo log 中 DB_TRX_ID 为 1 的地址

mysql支持undo操作吗?Undo日志到MVCC实现原理(10)

第三步:事务3,修改 name 为 unknown,流程如下

1、事务开始,对数据行加排它锁
2、将该行数据记录到 undo log,事务ID 为 2 的数据行
3、修改该行数据 name 为 unknown,DB_TRX_ID 修改为 3,DB_ROLL_PTR 指向 步骤 2 中记录的 undo log
4、提交事务,释放排它锁

当前最新数据如下,其中 0x65461234 指向 undo log 中 DB_TRX_ID 为 2 的地址

mysql支持undo操作吗?Undo日志到MVCC实现原理(11)

可以看出,通过 DB_ROLL_PTR 指针,同一行数据的多个版本形成了一个单向链表,链表中除第一条记录,都存储在 undo log。

ReadView 结构

通过 undo log 实现数据的多版本存储,接下来需要处理的就是数据读取的问题,该读取哪个版本的数据?ReadView 就是用来做可见性判断的。

ReadView 结构有几个重要的变量

  • creator_trx_id:当前事务的ID,每个事务都会拥有一个ID,是一个递增的编号。
  • trx_ids:Read View创建时其他未提交的活跃事务ID列表。
  • low_limit_id:目前出现过的最大的事务ID 1,即下一个将被分配的事务ID。
  • up_limit_id:活跃事务列表 trx_ids 中最小的事务ID,如果 trx_ids为空,则 up_limit_id 为 low_limit_id。

InnoDB 会在事务执行第一个 select 语句的时候创建 ReadView,读取某行记录的时候,根据该行的DB_TRX_ID 与 ReadView 进行可见性分析,判断读取哪个版本的数据。

具体的可见性比较算法如下:

  • 判断 DB_TRX_ID < up_limit_id,如果成立,表明 最新修改该行的事务当前事务 创建 ReadView 之前就已经提交,所以 DB_TRX_ID 版本对 当前事务 是可见的,否则进入下一个判断
  • 判断 DB_TRX_ID >= low_limit_id,如果成立,表明 最新修改该行的事务当前事务 创建 ReadView 之后才修改该行,所以DB_TRX_ID 版本对 当前事务 是不可见的
  • 判断 DB_TRX_ID 是否在活跃事务列表 trx_ids 中如果在,表明在 当前事务 创建 ReadView 时,最新修改该行的事务 还在活跃中,尚未提交,所以 DB_TRX_ID 版本对 当前事务 是不可见的。如果不在,表明在 当前事务 创建 ReadView 之前,最新修改该行的事务 就已经提交,所以 DB_TRX_ID 版本对 当前事务 是可见的。
  • DB_TRX_ID 版本对 当前事务 不可见,则根据 DB_ROLL_PTR 取出上一个版本数据,使用其 DB_TRX_ID 重新判断一次。

看完规则后,也许脑瓜子是嗡嗡的,那么接下来做一个模拟分析

mysql支持undo操作吗?Undo日志到MVCC实现原理(12)

事务2 执行快照读时,数据库为该行数据创建一个 ReadView,此时事务1、3处于活跃状态,事务4已提交,那么 ReadView 中各个变量的值如下:

trx_ids = [1 3] low_limit_id = 4 1 = 5 up_limit_id = 1 123

事务2读取最新记录,该记录的 DB_TRX_ID = 4,根据上文中的规则

  • 判断 DB_TRX_ID < up_limit_id,不成立,下一步
  • 判断 DB_TRX_ID >= low_limit_id,也不成立,下一步
  • 判断 DB_TRX_ID 是否在 trx_ids 集合中,结果为否,符合可见性条件。

所以事务4提交的最新结果对事务2是可见的。

再来一个更复杂的模拟分析,原始金额为 500,DB_TRX_ID 为 100

mysql支持undo操作吗?Undo日志到MVCC实现原理(13)

MVCC实现RR/RC事务隔离级别

前文中给出的示例都是基于 RR 隔离级别,下文给出一个 RR 隔离级别的简单示例,方便与 RC 隔离级别进行比较。

RR 隔离级别

mysql支持undo操作吗?Undo日志到MVCC实现原理(14)

RC 隔离级别

mysql支持undo操作吗?Undo日志到MVCC实现原理(15)

都是使用 MVCC 进行实现,同样的操作需要得到不同的结果,区别就在于创建 ReadView 的时机,RR 隔离级别只在第一个 select 语句时创建,而 RC 隔离级别则是每一个 select 语句都创建。

猜您喜欢: