Redo_Log**(重做日志)
redo log是物理日志,适用于崩溃恢复(crashsafe)
redo log 记录的是事务提交时数据页的物理修改(比如 对 XXX 表空间中的 YYY 数据页 ZZZ 偏移量的地方做了AAA 更新)。它不是随着事务的提交才写入的,而是在事务的执行过程 中,便开始写入 redo 中。
Buffer Pool:缓冲池是主内存中的一个区域,里面可以缓存磁盘上经常操作的数据,在执行增删改查操作时,先操作缓冲池中的数据(若缓冲池没有数据,则从磁盘加载并缓存),然后再以一定频率刷新到磁盘,从而减少磁盘IO,加快处理速度。缓冲池以Page页为单位,底层采用链表管理,Page分为三类:空闲Page、clean Page、脏页。
数据是存放在磁盘中的,但每次读写数据都需要磁盘IO,效率会很低。因此,InnoDB提供了缓存(Buffer Pool),Buffer Pool中包含了磁盘中部分数据页的映射,作为访问数据库的缓冲。当从数据库读取数据时,会首先从Buffer Pool中读取,如果没有,则会去磁盘读取后放入Buffer Pool。当数据库写入数据时,会先写入Buffer Pool,Buffer Pool中修改的数据会定期刷新到磁盘中(这个过程称为刷脏)。
Buffer Pool的使用大大提高了读写数据的效率,但是也带来了新的问题:如果MySQL宕机,而此时Buffer Pool中修改的数据还没刷新到磁盘,就会导致数据的丢失,事务的持久性也无法保证。
于是,redo log被引入来解决这个问题。当数据修改时,除了修改Buffer Pool中的数据,还会再redo log记录这次操作。当事务提交时,会调用fsync接口对redo log进行刷盘。如果MySQL宕机,重启时可以读取redo log中的数据,对数据库进行恢复。redo log采用的是WAL(Write-ahead logging,预写日志),所有修改先写入日志,再更新到Buffer Pool,保证了数据不会因MySQL宕机而丢失,从而满足了持久性要求。
既然redo log也是需要在事务提交时将日志写入磁盘,为什么它比刷脏快?主要原因有
- 刷脏是随机IO,因为每次修改数据位置随机,但redo log是追加操作,属于顺序IO。
- 刷脏是以数据页(page)为单位的,MySQL默认页大小是16KB,一个Page上的一个小修改都要整页写入。而redo log中只包含了真正需要写入的部分,无效IO大大减少。
为什么要有redo log?
-
事务提交后,必须将事务对数据页的修改刷(fsync)到磁盘上,才能保证事务的ACID特性。
-
这个刷盘,是一个随机写,随机写性能较低,如果每次事务提交都刷盘,会极大影响数据库的性能。Innodb 存储引擎设计了一个缓冲池(Buffer Pool):
为什么要有buffer pool?(加载的数据页在buffer pool中)
- 当读取数据时,如果数据存在于 Buffer Pool 中,客户端就会直接读取 Buffer Pool 中的数据,否则再去磁盘中读取。
- 当修改数据时,如果数据存在于 Buffer Pool 中,那直接修改 Buffer Pool 中数据所在的页,然后将其页设置为脏页(该页的内存数据和磁盘上的数据已经不一致),为了减少磁盘I/O,不会立即将脏页写入磁盘,后续由后台线程选择一个合适的时机将脏页写入到磁盘。
有了buffer pool为什么还有redo log?
有了Buffer Pool 提高了读写效率,但是如果没有 redo log,当 Buffer Pool 的数据还没有刷新到磁盘上时,即脏页还没有刷盘时,数据库突然宕机或重启,这些数据就会丢失。redo log就可以作为保险。
redo log 包括两部分:一个是内存中的日志缓冲(redo log buffer),另一个是磁盘上的日志文件
(redo log file)。MySQL 每执行一条 DML(增删改查)/DDL(变更表结构、数据类型) 语句,先将记录存放入日志缓冲中,再按一定频率将其刷新到日志文件里面(每一秒中后台线程会将重做日志缓冲刷新到日志文件)。后续 InnoDB 引擎会在适当 的时候(比如 redo log 日志写满了或者空闲的时候或者正常关闭的时候)再一次性将多个操作记录更新 到磁盘里面。这种先写日志,再写磁盘的技术就是 MySQL 里经常说到的 WAL(Write-Ahead Logging) 技术。
redo log的crash-safe能力:
InnoDB 的 redo log 是固定大小的,比如可以配置为一组 4 个文件,每个文件的大小是 1GB,那么此日志 最多可以记录 4GB 的操作,从头开始写,写到末尾就又回到开头循环写。write pos 是当前记录的位置,一边写一边后移;checkpoint 是当前要擦除的位置,也是往后推移并且循环的,擦除记录前要把记录更新到数据文件。有了 redo log,InnoDB 就可以保证即使数据库发生异常重启,之前提交的记录都不会丢失,这个能力称 为 crash-safe。即数据库发生宕机时,数据库不需要重做所有的日志,因为 checkpoint 之前的页都已经刷新回磁盘。故数据库只需对 checkpoint 后的重做日志进行恢复,这样也大大缩短了恢复的时间。
redo log最终落盘的步骤如何?
-
首先,事务提交的时候,会写入Log Buffer,这里调用的是MySQL自己的函数WriteRedoLog;
-
接着,只有当MySQL发起系统调用写文件write时,Log Buffer里的数据,才会写到OS cache。注意,MySQL系统调用完write之后,就认为文件已经写完,如果不flush,什么时候落盘,是操作系统决定的;
-
最后,由操作系统(当然,MySQL也可以主动flush)将OS cache里的数据,最终fsync到磁盘上;
redo log 要写到磁盘,数据也要写磁盘,为什么要多此一举?
-
这里就是将“每次写”优化为“批量写”,以提高操作系统性能。
-
刷新一个完整的数据页太浪费。InnoDB 以页为单位来进行磁盘 I/O,而如果有时仅仅修改了某个页面的一个字节就需要将一个完整的页面从内存中刷新到磁盘中,显然就有些浪费了。
-
写入 redo log 的方式使用了追加操作(一次性去磁盘里边找就好了,而不是每次都去磁盘找), 所以磁盘操作是顺序写,而写入数据需要先找到写入位置,然后才写到磁盘,所以磁盘操作是随机写。随机 I/O 刷新慢。一个事务中可能包含多条语句,即使是一条语句也可能修改许多页面(比如一条插入语句涉可能涉及多个索引,那么都需要进行更新),而这些页面有可能并不相邻,那么在持久化到磁盘中时,可能进行许多的随机 I/O,较为缓慢。
数据库为什么要缓冲数据到Log Buffe里,而不是直接write呢?
这也是“每次写”优化为“批量写”思路的体现,以提高数据库性能。
redo log的三层架构,MySQL做了一次批量写优化,OS做了一次批量写优化,确实能极大提升性能,但有什么副作用吗?
这个副作用,就是可能丢失数据:
(1)事务提交时,将redo log写入Log Buffer,就会认为事务提交成功;
(2)如果写入Log Buffer的数据,write入OS cache之前,数据库崩溃,就会出现数据丢失;
(3)如果写入OS cache的数据,fsync入磁盘之前,操作系统奔溃,也可能出现数据丢失;
redo log刷盘时机:
-
MySQL 正常关闭时
-
当 redo log buffer 中记录的写入量大于 redo log buffer 内存空间的一半时,会触发落盘
-
每次事务提交时【具体不同策略不一样】:
innodb_flush_log_at_trx_commit
-
0 :表示每次事务提交时不进行刷盘操作(MySQL 进程的崩溃会导致上一秒钟所有事务数据的丢失)
-
1 :表示每次事务提交时都将进行刷盘操作(默认值,只要事务提交成功,redo log记录就一定在硬盘里,不会有任何数据丢失)
-
2 :表示每次事务提交时都只把 redo log buffer 内容写入 page cache(如果仅仅只是MySQL挂了不会有任何数据丢失,只有操作系统崩溃的情况下,上一秒钟所有事务数据才可能丢失)
-
InnoDB 存储引擎有一个后台线程,每隔1 秒,就会把 redo log buffer 中的内容写到文件系统缓存(page cache),然后调用 fsync 刷盘。
binlog
binlog 是逻辑日志(逻辑修改记录的是操作逻辑语句,而不是数据),主要用于数据备份和主从复制。
MySQL 的 binlog 是记录所有数据库表结构变更(例如 CREATE、ALTER TABLE)以及表数据修改 (INSERT、UPDATE、DELETE)的二进制日志。binlog 不会记录 SELECT 和 SHOW 这类操作,因为这类 操作对数据本身并没有修改。
三种binlog:
- STATEMENT:记录每一条修改数据的 SQL 原文。可能主从库对于sql的执行不一致。
- ROW:逻辑日志,记录数据行的变化。占空间较大
- MIXED(默认):两种格式混用,有数据风险的使用ROW,无风险的用STATEMENT
redolog 和 binlog 有什么区别:
- binlog是 Server 层生成的日志,redolog是 innoDB 特有的
- 刷盘时机不同:binlog只在事务提交的时候才写入,redolog有三种不同的刷盘策略。
- 用途不同:binlog用于数据备份和主从复制,redolog用于宕机后的数据恢复
- 为什么 binlog 没有 crash-safe 功能:一个是循环写,一个是追加写。也就是说 redo log 只会记录 未刷盘的日志,已经刷入磁盘的数据都会从 redo log 这个有限大小的日志文件里删除。binlog 是追加日志,保存的是全量日志。当数据库 crash 后,想要恢复未刷盘但已经写入 redo log 和 binlog 的数据, binlog 是无法恢复的。虽然 binlog 拥有全量的日志,但没有一个标志让 innodb 判断哪些数据已经刷盘, 哪些数据还没有。但 redo log 不一样,只要刷入磁盘的数据,都会从 redo log 中抹掉,数据库重启后, 直接把 redo log 中的数据都恢复至内存就可以了。
两阶段提交
-
执行一个DML语句时,会执行如上图的操作
- 将修改后的数据存入缓冲池(Buffer Pool)中,等待刷脏。
- 在Log Buffer中写入重做日志(Redo Log),并将日志状态设置为Prepare。
- 返回MySQL服务层,记录BinLog日志。
- 将Log Buffer中的日志文件状态设为Commit,并等待日志文件存入盘中。
-
两阶段提交:
- prepare 阶段:将 XID(当前事务的 ID) 写入到 redo log,同时将 redo log 对应的事务状态设置为 prepare,然后将 redo log 刷新到硬盘;
- commit 阶段:把 XID 写入到 binlog,然后将 binlog 刷新到磁盘,接着调用引擎的提交事务接口,将 redo log 状态设置为 commit(将事务设置为 commit 状态后,刷入到磁盘 redo log 文件,所以 commit 状态也是会刷盘的)
-
为什么要两阶段提交:
-
先写 redo log 直接提交,然后写 binlog,假设写完 redo log 后,机器挂了,binlog 日志没有被写入,那么机器重启后,这台机器会通过 redo log 恢复数据,但是这个时候 binlog 并没有记录该数据,后续进行机器备份的时候,就会丢失这一条数据,同时主从同步也会丢失这一条数据。
-
先写 binlog,然后写 redo log,假设写完了 binlog,机器异常重启了,由于没有 redo log,本机是无法恢复这一条记录的,但是 binlog 又有记录,那么和上面同样的道理,就会产生数据不一致的情况。
-
-
两阶段提交如何完成崩溃恢复:
- 判断 redo log 是否完整 (commit),如果判断是完整的,就立即提交。
- 如果 redo log 只是预提交但不是 commit 状态,这个时候就会去判断 binlog 是否完整:
- 如果 binlog 中没有当前内部 XA 事务的 XID,说明 redolog 完成刷盘,但是 binlog 还没有刷盘,binglog不完整,则回滚事务。不完整对应时刻 A 崩溃恢复的情况。
- 如果 binlog 中有当前内部 XA 事务的 XID,说明 redolog 和 binlog 都已经完成了刷盘,binglog完整,则提交事务。对应时刻 B 崩溃恢复的情况。