likes
comments
collection
share

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

作者站长头像
站长
· 阅读数 11

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

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

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

事务日志

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

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

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

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

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

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

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

介绍完事务日志的背景,接下来介绍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如何实现原子性、持久性MySQL如何实现原子性、持久性

从图中可以看到,假设系统在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覆盖式写入。

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

做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更像一个链表。

事务回滚时,便是跟着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、是一个历史数据版本链表,通过这个链表回滚数据