参考博客1(建议先通读该博客)介绍了MySQL通过Undo+Redo Log的机制实现了事务的原子性、一致性和持久性(关于事务的隔离性是通过锁机制来保障的,请参考我的另一篇博文MySQL常见的七种锁详细介绍)。文章中提到:
- 用Undo Log实现原子性和持久化的事务的简化过程 | - 用Undo + Redo实现原子性和持久化的事务的简化过程 |
---|---|
假设有A、B两个数据,值分别为1,2。 | 假设有A、B两个数据,值分别为1,2. |
A.事务开始. | A.事务开始. |
B.记录A=1到undo log. | B.记录A=1到undo log. |
C.修改A=3. | C.修改A=3. |
D.记录B=2到undo log. | D.记录A=3到redo log. |
E.修改B=4. | E.记录B=2到undo log. |
F.将undo log写到磁盘. | F.修改B=4. |
G.将数据写到磁盘. | G.记录B=4到redo log. |
H.事务提交 | H.将redo log写入磁盘. |
I.事务提交 |
但文章中关于redo log的说明存在前后矛盾:
一方面,文章说在进行恢复时,重做所有事务,包括未提交的事务和回滚了的事务,然后通过undo Log回滚那些未提交的事务;
另一方面又说,在重做redo log时,不关心事务性,在恢复时,没有BEGIN,也没有COMMIT或ROLLBACK的行为。
为什么说这两者相互矛盾呢?文中说了undo log是作为redo log的数据存储在redo log中的。但日志中却并未标记事务的开始,提交或回滚。那么如何能辨别哪些事务未提交呢?按照博客中的说法,redo log中去掉undo log,剩下的日志只能处理回滚了的事务和正常提交的事务,对于未提交的事务是无法处理的。
对于这个疑惑,参考博客2中的描述是比较合理的。在参考博客2中说到:
对于某事务T,在log file的记录中必须开始于事务开始标记(比如“start T”),结束于事务结束标记(比如“end T”、”commit T”)。在系统恢复时,如果在log file中某个事务没有事务结束标记,那么需要对这个事务进行undo操作,如果有事务结束标记,则redo。这里的结束标记就包含commit和rollback。
除此之外,参考博客2中还引出了两个重要的名词:检查点(checkpiont)和幂等性(idempotence)问题。首先我们来说说幂等性。博客中要求日志文件中的操作记录应该具有幂等性,原因是在故障恢复中日志中的记录可能会重复执行多次,如果操作记录不满足幂等性,会造成数据错误。举例来说就是,我们在写增删改等语句的时候,可能并没有明确受影响的记录范围,比如我们在写insert的语句的时候,往往不会限定自增主键id的值,但当Mysql将我们的insert语句记录到redo log日志中时,一定会明确其id值(实际上redo log中存储的是更新后的物理数据页,即物理日志),否则如果执行多次恢复就无法保证幂等性(当然对于insert还需要忽略因记录已经存在而导致插入失败的错误)。再比如对update操作,我们在写update语句的时候,可能会写成在原值的基础上增加或减少一个数值,但redo log中的记录也是变更后的最终值,而不是增量值。除此之外,还有一点非常重要:我们在做多次恢复操作期间,数据库是不能对外提供服务的,否则恢复操作可能会因数据变更而导致数据出错,幂等性也就无从谈起。
再来说说checkpoint。要说checkpoint就不得不提LSN(Log Sequence Number)。我们先了解下为什么要有这两个概念。参考博文3中提到:
InnoDB采用Write Ahead Log策略来防止宕机数据丢失,即事务提交时,先写重做日志,再修改内存数据页,这样就产生了脏页。既然有重做日志保证数据持久性,查询时也可以直接从缓冲池页中取数据,那为什么还要刷新脏页到磁盘呢?如果重做日志可以无限增大,同时缓冲池足够大,能够缓存所有数据,那么是不需要将缓冲池中的脏页刷新到磁盘。但显然,服务器的内存是有限的,缓冲池不能缓存全部数据,而且重做日志无限增大,宕机后重做全部日志则的恢复时间会过长。前面提到过,恢复期间,数据库是不能对外提供服务的。为了缩短数据库恢复时间,我们需要一种机制,定期地将日志和数据页持久化到磁盘。该机制还要记录哪些日志在数据恢复时需要执行,哪些已经不需要执行。该机制就是checkpoint,而checkpoint是通过LSN实现的。具体我们看参考博文4:
checkpoint是log日志对数据页刷新到磁盘的操作的检查点,通过LSN号保存记录,作用是当发生宕机等crash情况时,再次启动时会查询checkpoint点,将该检查点之后发生的事务修改恢复到磁盘。
InnoDB引擎通过LSN来标记版本,LSN是日志空间中每条日志的结束点,用字节偏移量来表示。每个数据页有LSN,redo log也有LSN,Checkpoint也有LSN。可以通过命令show engine innodb status来观察。
mysql> show engine innodb status\G;
---
LOG
---
Log sequence number 10623965866
Log flushed up to 10623965866
Pages flushed up to 10623965866
Last checkpoint at 10623965857
0 pending log flushes, 0 pending chkp writes
13 log i/o‘s done, 0.81 log i/o‘s/second
Last checkpoint at就是系统最后一次刷新buffer pool中页数据到磁盘的checkpoint,checkpoint是和redo log进行关联操作的,也就记录在redo log中,checkpoint记录在redo log第一个文件的头部,存储两个值循环更替修改。
LSN(log sequence number)日志序列号,5.6.3之后占用8字节,LSN主要用于发生crash时对数据进行recovery,LSN是一个一直递增的整型数字,表示事务写入到日志的字节总量。
LSN不仅只存在于重做日志中,在每个数据页头部也会有对应的LSN号,该LSN记录当前页最后一次修改的LSN号,用于在recovery时对比重做日志LSN号决定是否对该页进行恢复数据。前面说的checkpoint也是有LSN号记录的,LSN号串联起一个事务开始到恢复的过程。
到此我们明白了checkpoint和LSN的工作机制:日志空间中的每条日志对应一个LSN值,而在数据页的头部也记录了当前页最后一次修改的LSN号,每次当数据页刷新到磁盘后,会去更新日志文件中的checkpoint,以减少需要恢复执行的日志记录。极端情况下,数据页刷新到磁盘成功后,去更新checkpoint时如果宕机,则在恢复过程中,由于checkpoint还未更新,则数据页中的记录相当于被重复执行,这个时候,前面介绍的幂等性的作用就体现出来了。
参考博客:
1. https://blog.csdn.net/chast_cn/article/details/50910861 MySQL redo与undo
2. https://blog.csdn.net/kobejayandy/article/details/50885693 理解数据库中的undo日志、redo日志、检查点
3. https://blog.csdn.net/melody_mr/article/details/48930739 InnoDB Redo Flush及脏页刷新机制深入分析
4. http://www.mamicode.com/info-detail-1264451.html mysql 之 checkpoint和LSN详解
5. https://blog.csdn.net/Baisitao_/article/details/104723795?utm_source=app MySQL事务是怎么实现的
6. https://www.bbsmax.com/A/D854eYBWdE/ savepoint原理
7. https://www.cnblogs.com/binyang/p/11260126.html 日志系统 SQL更新语句的执行
8.https://blog.csdn.net/qq_38125183/article/details/80652557 redo&undo日志解析
9.详细分析MySQL事务日志(redo log和undo log) 重要
10.https://www.zhihu.com/question/368847138/answer/996629614 InnoDB如何保证redolog的完整性?
11.https://www.jianshu.com/p/fdae2e30b9fa?from=timeline Redo Log——第一篇
12.https://blog.csdn.net/bohu83/article/details/81481184 MySQL · 引擎特性 · InnoDB redo log
13.https://blog.csdn.net/bohu83/article/details/81568341 MySQL · 引擎特性 · InnoDB undo log
14.https://www.cnblogs.com/xinysu/p/6586386.html MySQL崩溃恢复过程 重要
15、mysql日志系统 SQL 逻辑日志 物理日志 重要
16、https://blog.csdn.net/anying5823/article/details/104675987/ MySQL原子性与持久性的保证(undo log, redo log与binlog) 重要
17、https://blog.51cto.com/yanzongshuai/2090932 解析MySQL binlog --(6)XID_EVENT、ROTATE_EVENT及stop :当事务提交时,不论是statement还是row格式的binlog都会添加一个XID_EVENT作为事务的结束。该事件记录了该事务的ID。在mysql进行崩溃恢复时根据binlog中提交的情况来决定是否提交redo log中处于prepared状态的事务。
18、https://www.cnblogs.com/klvchen/p/10861850.html MySQL 重要参数 innodb_flush_log_at_trx_commit 和 sync_binlog