redo log、undo log、redo/undo log
redo log 和 undo log
redo log 是重做日志,提供 前滚 操作;undo log 是回退日志,提供 回滚 操作。
只用 undo log 实现原子性和持久性的缺陷:
- 事务提交前需要将 Undo Log 写磁盘(提供可回滚功能,保证原子性),这会造成多次磁盘 IO(不考虑各种优化例如 SQL 解析优化等),这些 IO 算是顺序 IO;
- 事务提交后需要将数据立即更新到数据库中,这又会造成至少一次磁盘 IO,这是一次随机 IO。
如何优化?事务提交后如果能够将数据缓存一段时间,而不是立即更新到数据库,就能将一次次的随机 IO 打包变成一次 IO,可以提高性能。但是这样就会丧失事务的持久性。因此引入了另外一种机制来实现持久化,即 redo log。redo 解决的问题之一就是事务执行过程中的强制刷脏。
在事务提交前,只要将 Redo Log 持久化即可,不需要将数据持久化。当系统崩溃时,虽然数据没有持久化,但是 Redo Log 已经持久化。系统可以根据 Redo Log 的内容,将所有数据恢复到最新的状态。
InnoDB 有 buffer pool(简称bp)。bp 是 物理页 的缓存,对 InnoDB 的任何修改操作都会首先在 bp 的 page 上进行,然后这样的页面将被标记为 dirty 并被放到专门的flush list 上,后续将由专门的刷脏线程阶段性的将这些页面写入磁盘。这样的好处是避免每次写操作都操作磁盘导致大量的随机 IO,阶段性的刷脏可以将多次对页面的修改 merge 成一次IO 操作,同时异步写入也降低了访问的时延。
然而,如果在 dirty page 还未刷入磁盘时,server非正常关闭,这些修改操作将会丢失,如果写入操作正在进行,甚至会由于损坏数据文件导致数据库不可用。为了避免上述问题的发生,Innodb 将所有对页面的修改操作写入一个专门的文件,并在数据库启动时从此文件进行恢复操作,这个文件就是 redo log file。这样的技术推迟了 bp 页面的刷新,从而提升了数据库的吞吐,有效的降低了访问时延。带来的问题是额外的写 redo log 操作的开销(顺序 IO,比随机 IO 快很多),以及数据库启动时恢复操作所需的时间。
划重点:
- redo log 通常是 物理 日志,记录的是 数据页 的物理修改,而不是某一行或某几行修改成怎样怎样,它用来恢复提交后的物理数据页(恢复数据页,且只能恢复到最后一次提交的位置)。
2.undo log 用来回滚行记录到某个版本。undo log 一般是逻辑日志,根据每行记录进行记录。
redo/undo log
将二者结合,提高效率。
要从两个角度来优化,一个就是尽可能减少写入硬盘(即多个事务合并成一次落盘),另一个就是尽量顺序写入(HDD 的随机写入性能远差于顺序写入)。
Undo 记录某 数据 被修改 前 的值,可以用来在事务失败时进行 rollback;
Redo 记录某 数据块 被修改 后 的值,可以用来恢复未写入 data file 的已成功事务更新的数据。
即,
- Redo Log 保证事务的持久性
- Undo Log 保证事务的原子性(在 InnoDB 引擎中,还用 Undo Log 来实现 MVCC)
比如某一时刻数据库 DOWN 机了,有两个事务,一个事务已经提交,另一个事务正在处理。数据库重启的时候就要根据日志进行前滚及回滚,把已提交事务的更改写到数据文件,未提交事务的更改恢复到事务开始前的状态。即,当数据 crash-recovery 时,通过 redo log 将所有已经在存储引擎内部提交的事务应用 redo log 恢复,所有已经 prepared 但是没有 commit 的 transactions 将会应用 undo log 做 roll back。
小细节总结:为什么只用 redo-log 或者只用 undo-log 不可以
- 假设只有 undo-log:那么就必须保证提交前刷脏完成,否则宕机时有些修改就在内存中丢失了,破坏了持久性。(这样带来了一个问题,那就是前面提到的性能差)
- 假设只有 redo-log:那么就不能随心所欲地在事务提交前刷脏,即无法支持大事务。(假如、某张表有 100 亿的 8 字节整数数据,就算不考虑其他东西带来的损耗,光 update 整张表至少要消耗 80G 的内存。如前所述,有了 undo-log,就可以随便刷脏。)
redo/undo log 和 binlog
两者区别还是挺多的,大致如下,
- 层次不同。redo/undo 是 innodb 引擎层维护的,而 binlog 是 mysql server 层维护的,跟采用何种引擎没有关系,记录的是所有引擎的更新操作的日志记录。
- 记录内容不同。redo/undo 记录的是 每个页/每个数据 的修改情况,属于物理日志+逻辑日志结合的方式(redo log 是物理日志,undo log 是逻辑日志)。binlog 记录的都是事务操作内容,binlog 有三种模式:Statement(基于 SQL 语句的复制)、Row(基于行的复制) 以及 Mixed(混合模式)。不管采用的是什么模式,当然格式是二进制的,
- 记录时机不同。redo/undo 在 事务执行过程中 会不断的写入,而 binlog 是在 事务最终提交前 写入的。binlog 什么时候刷新到磁盘跟参数
sync_binlog
相关。
binlog 三种模式对比
上面提到 binlog 有三种格式,各有优缺点:
- statement:基于SQL语句的模式,某些语句中含有一些函数,例如
UUID
NOW
等在复制过程可能导致数据不一致甚至出错。 - row:基于行的模式,记录的是行的变化,很安全。但是 binlog 的磁盘占用会比其他两种模式大很多,在一些大表中清除大量数据时在 binlog 中会生成很多条语句,可能导致从库延迟变大。
- mixed:混合模式,根据语句来选用是 statement 还是 row 模式。
扩展
解决上面提到的大事务的 bad case 并不只有使用 undo 日志这一个方案,其他方案比如,
- 用虚拟内存,开足够大
- 文档里直接写明不支持
- 加内存,加到够用为止
- 内存不够用就转储到硬盘的临时文件上
但这些方案要么性能太差,要么委屈客户,要么成本太高,因此 Mysql 和 Oracle 都选择了使用 undo 日志(虽然实现上各有不同)。
但是需要注意的是,OceanBase 由于采用了 LSM-Tree 的方案通过减小写放大和加内存的方式抬高了大事务的上限。这也算是一种解决思路。