MySQL如何实现原子性、持久性

2024年 3月 21日 71.1k 0

 前面已经说完了事务的四个特性以及事务隔离级别概念性的东西,接下来我们讲讲具体实现,以便能有个更深的印象与理解。

以下内容均指的是MySQL innodb存储引擎的实现

首先,我们先讲一个前置知识点:事务日志。

事务日志

由于磁盘的写入速度远远低于内存的速度,为了提高写入速度,数据库不会每写入一次数据就刷新一次磁盘,特别是更新数据的操作往往是随机写入。而对于磁盘而言,随机写入的操作要比顺序写入慢得多。因此数据库采取的策略是:数据更新到内存池中,然后再根据配置按一定间隔flush到磁盘。

这样一来有一个问题:当系统突然宕机,还没更新到磁盘的数据就丢失了。

为了解决这个问题,引入了「事务日志」的机制。

即每当数据变化时,就写一条更新日志到事务日志里,当系统宕机时,再根据事务日志恢复系统数据。

使用事务日志的另一个好处是:每次更新事务日志时,都是从事务日志的尾部插入一条日志,相当于顺序写入,此时磁盘的速度是很快的。

MySQL如何实现原子性、持久性-1​编辑

MySQL如何实现原子性、持久性-1

介绍完事务日志的背景,接下来介绍MySQL两个事务日志以及他们的实现与作用,undolog(回滚日志)和redolog(重做日志)。

Redolog

redolog,重做日志,记录了某个数据页做了什么修改,比如对XXX 表空间中的YYY 数据页ZZZ 偏移量的地方做了AAA 更新,每当执行一个事务就会产生这样的一条或者多条物理日志。

什么叫物理日志?我们知道innodb在底层存储时,是由数据页组成的,直接记录数据页变化的日志叫物理日志。

与物理日志相对的,叫逻辑日志,记录的一般是sql语句。

当数据库崩溃恢复,还未来得及落盘的数据就是通过redolog进行恢复。换句话说,已经提交的事务可以通过redolog恢复,进而实现了事务的持久性——在事务成功提交了之后,事务所变更的数据一定会保存起来,而不会因为任何故障导致数据丢失。

redolog的具体实现

redo log包含两部分:一个是内存中的redo log buffer,另一个是磁盘上的redo log file,mysql每执行一条DML语句,先将记录写入redo log buffer,后续某个时间点再一次性将buffer中的记录写到redo log file。

但用户空间下的缓冲区数据一般情况下是无法直接写入磁盘的,中间必须经过操作系统内核空间缓冲区( OS Buffer )。因此, redo log buffer 写入 redo log file 实际上是先写入 OS Buffer ,然后再通过系统调用 fsync() 将其刷到 redo log file磁盘中。

MySQL如何实现原子性、持久性-1

从图中可以看到,假设系统在os buffer这个阶段断电了,redo log最新数据没有落盘,是不是也会丢失数据。答案是肯定的(排除现在在硬件层面保障os buffer在断电时也有足够的时间保存到磁盘)。

为了平衡写入性能与数据安全,mysql提供了三种配置供用户选择:

参数值 含义
0(延迟写) 事务提交时不会将redo log buffer写到os buffer,而是每秒写入,同时调用fsync()写入磁盘。也就是设置为0时,当系统崩溃时,会丢失1s的数据
1(实时写,实时刷) 事务每次提交都会将redo log buffer写入os buffer并调用fsync()刷到磁盘,这种方式即使系统崩溃也不会丢失数据,但是IO性能较差
2(实时写,延迟刷) 每次提交都写入到os buffer,每秒调用fsync()将os buffer写入到磁盘,这种对比0来说,由于是写入到os buffer,当系统崩溃时,可以依赖系统的兜底机制将数据落盘,降低数据丢失的风险。

checkpoint

实际上redo log不可能无限大,为了节省空间,磁盘上的redo log以一个日志文件组的形式出现,在逻辑使用上,像一个环形链表。假设文件组的文件编号是从A-Z,数据从编号A一直写到编号Z文件,再继续从A-Z覆盖式写入。

MySQL如何实现原子性、持久性-1

为了避免后写入的redo log影响了先写入的redo log,innoDB的设计者提出了checkpoint的概念。

MySQL如何实现原子性、持久性-1

做checkpoint时,将checkpoint之前对应的数据页落盘,每次数据恢复时只从checkpoint开始。

从图中按,数据段 1 代表中数据恢复时应该执行的redo log。数据段 2 代表本次执行checkpoint时要写入的磁盘脏页对应的redo log。

Undolog

undolog叫做回滚日志,在事务变更操作之前写入一条相反的操作到undo log,通过它可以实现事务的回滚操作。举个简单的例子,当sql语句是insert时,则执行之前数据库会往undo log写入一条delete语句,回滚时通过执行这条delete语句实现回滚;当sql语句是update操作时,undo log则会记录旧值。

可以看到,redolog是物理日志(记录数据页的变化),undolog则是逻辑日志。

undolog的数据管理

InnoDB对undo log的管理采用段的方式,也就是回滚段。每个回滚段记录了1024个回滚段(undo segment)。每个事务只会使用一个回滚段,在事务进行的过程中,数据的修改会被记录到回滚段中。

与redolog一样,undolog也分为内存池和磁盘两部分。对undolog写入时会先写入undo log buffer,后台线程再按一定时间去刷盘。

Undolog的具体使用

undo log有两种类型:insert和update(delete语句的undo log类型也是update),在undolog记录上有一个字段来标识这两种类型。这一点比较简单,没有必要细讲。

在逻辑上,undo更像一个链表。

MySQL如何实现原子性、持久性-1

事务回滚时,便是跟着next指针一步步回溯原始数据。undo no是每条undo log的编号。

怎么知道回滚的事务对应的undolog链是哪一条呢?

事实上,innodb的每行数据都有三个隐藏字段:

  • DB_TRX_ID:最后对该行进行插入或修改的事务ID
  • DB_ROLL_PTR:回滚指针,指向该数据的上一个版本,本质上就是指向undo log的指针。
  • DB_ROW_ID,隐藏主键。

DB_ROW_ID比较简单,具体作用可以留到索引那里讲讲。

主要是DB_ROLL_PTR,当事务回滚时,便是沿着DB_ROLL_PTR,回滚对应的undo log数据即可。

至于剩下的DB_TRX_ID,主要作用则在后面的事务隔离环节。

总结

  • 事务日志的概念       

       1、为了优化数据更新速度,数据只更新到内存池,内存池定时刷回磁盘。为了防止更新丢失,数据更新前先写一条事务日志。

        2、事务日志是顺序写入,速度比随机写入快很多

  • redolog

        1、已经提交的事务通过redolog恢复数据

        2、数据存储也分为buffer和file,提供了三种不同的配置将数据从buffer刷到file:延时写、实时写实时刷、实时写,延迟刷

        3、redolog逻辑是是环形写入,通过checkpoint清理过期的redolog

  • undolog    

        1、是一个历史数据版本链表,通过这个链表回滚数据

相关文章

Oracle如何使用授予和撤销权限的语法和示例
Awesome Project: 探索 MatrixOrigin 云原生分布式数据库
下载丨66页PDF,云和恩墨技术通讯(2024年7月刊)
社区版oceanbase安装
Oracle 导出CSV工具-sqluldr2
ETL数据集成丨快速将MySQL数据迁移至Doris数据库

发布评论