MySQL之 bin log、redo log和undo log 简介
前言
日志是MySQL
数据库的重要组成部分,记录着数据库运行期间各种状态信息。MySQL
中日志类型有很多种,但对于开发来说,最常见和最重要的就是binlog
、redolog
和undolog
。本篇文章主要对这三种日志类型做一个简要的介绍。
前置知识
简单说一下,逻辑日志和物理日志的区别吧
- 逻辑日志:可以简单得理解为
sql
语句,如insert into tbl value(...)
; - 物理日志:
MySQL
中数据都是保存在数据页中的,物理日志记录的是数据页上的变更,如新增的一条数据具体存储在哪个数据页上的哪一行;
binlog
binlog
是MySQL Server
层记录的日志,也就是说,不管MySQL
使用的什么存储引擎,都会有bin log
产生。binlog
是MySQL
中最重要的日志,它记录了所有的DDL
和DML
(除了查询语句)语句,即所有修改数据的操作,以二进制的形式存储在磁盘中,binlog
是一种逻辑日志。
binlog 作用
- 主从复制:在
Mater
端开启binlog
,然后将binlog
发送到各个Slave
端,Slave
端重放binlog
从而达到主从数据一致; - 数据恢复:基于时间点,可以通过
mysqlbinlog
工具来恢复数据;
binlog 主从复制原理
MySQL
主从同步主要依靠binlog
来实现。这里简单介绍一下基本原理。
流程大致如下:
-
主节点 binlog dump 线程:当从节点连接主节点时,主节点会创建一个
log dump
线程,用于发送binlog
的内容。在读取binlog
中的操作时,此线程会对主节点上的binlog
加锁,当读取完成,甚至在发动给从节点之前,锁会被释放 -
从节点I/O线程:当从节点上执行
start slave
命令之后,从节点会创建一个I/O线程用来连接主节点,请求主库中更新的binlog
。I/O
线程接收到主节点binlog dump
进程发来的更新之后,保存在本地relaylog
中; -
从节点SQL线程:
SQL
线程负责读取relaylog
中的内容,解析成具体的操作并执行,最终保证主从数据的一致性;
binlog的内容
上面说了,binlog
是一种逻辑日志,可以简单得理解为sql
语句,但是实际上还包含着执行的sql
语句的反向逻辑。delete
对应着delete
本身以及反向的insert
信息;update
包含着对应的update
执行前后数据行的相关信息;insert
包含自身的insert
以及对应的delete
信息。
binlog的格式
binlog
共有三种格式,分别是statement
、row
以及mixed
。MySQL 5.7.7
版本之前默认使用的是statement
,MySQL 5.7.7
之后默认使用的是row
。日志的格式可以通过my.ini
配置文件中的binlog-format
来修改。
-
statement
:基于sql
语句的复制(statement-based replication,SBR
),每一条修改数据的sql
语句都会记录到binlog
中。- 优点:不需要具体记录某一行的变化,节约空间,减少
io
,提高性能; - 缺点:在执行
sysdate()
或者sleep()
等操作的时候,可能导致主从数据不一致的情况;
- 优点:不需要具体记录某一行的变化,节约空间,减少
-
row
:基于行记录的复制(row-based replication,RBR
),不记录sql
语句上下文相关信息,而是记录哪条记录被修改的细节。- 优点:非常详细地记录每一行记录修改的细节,因而不会出现数据无法被正确复制的情况;
- 缺点:由于会非常详细地记录每一条记录修改的细节,这样会产生大量的日志内容。假设现在有一条
update
语句,修改了很多条记录,则每条修改记录都会记录到binlog
中。特别地,alter table
这个操作,由于表结构的变化,每行记录都会发生变化,导致日志量暴增;
-
mixed
:根据上面所说的,statement
和row
各有优缺点,因此出现了mixed
这个版本,将这二者进行混合。一般情况下使用statement
格式来进行保存,当遇到statement
无法解决时,切换为row
格式来进行保存。
新版本(MySQL 5.7.7
之后)默认使用的row
格式,这里的row
也做了相应的优化,在遇到alter table
这个操作时采用statement
格式进行记录,其余操作仍然使用row
格式。
binlog的刷盘时机
对于InnoDB
存储引擎来说,只有在事务提交的时候才会记录binlog
,此时记录还在内存中,MySQL
通过sync_binlog
来控制binlog
的刷盘时机,取值范围为0-N
:
- 0:不强制刷到磁盘,由系统自行判断何时写入磁盘中;
- 1:每次提交后都要将
binlog
写入磁盘中; N
:每N
个事务,才会将binlog
写入磁盘中;
binlog的物理文件大小
在my.ini
配置文件中,可以通过max_binlog_size
来配置binlog
的大小。当日志量超过binlog
文件的大小时,系统会重新生成一个新的文件来继续保存文件。当一个事务比较大时,或者是当日志越来越多的时候,此时占据的物理空间太大怎么办?MySQL
提供了一种自动删除的机制,还是在my.ini
配置文件中,可以通过配置expire_logs_days
这个参数来解决,单位为天。当这个参数为0,表示永不删除;为N
时,表示第N
天后自动删除。
redolog
redolog
是InnoDB
引擎专有的日志系统。主要是用来实现事务的持久性以及实现crash-safe
功能。redolog
属于物理日志,记录的是sql
语句执行之后数据页上的具体修改内容。
redolog的作用
我们都知道,当MySQL
运行的时候,会将数据从磁盘中加载到内存当中。当执行sql
语句对数据进行修改时,修改后的内容其实都只是暂时保存到内存当中,如果此时断电或者出现其他情况导致机器宕机,这些修改就会丢失。因而,当修改完数据之后,MySQL
会寻找机会将这些内存中的记录刷回到磁盘当中。但这就出现一个性能问题,主要有两个方面:
InnoDB
中是以页为数据单位与磁盘进行交互的,而一个事务很可能只是修改了一个页上的几个字节,如果将一个完整的数据页刷回磁盘当中,浪费资源;- 一个事务可能涉及到多个数据页,这些数据页只是逻辑上连续,在物理上并不连续,使用随机
IO
性能太差;
因此,MySQL
设计了redolog
来记录事务对数据页具体做了哪些修改,之后将redolog
再刷回磁盘当中。你可能会有疑惑,本来就是想减少io
,这不又加上一次io
么?InnoDB
的设计者在设计之初就已经考虑到了这些。redolog
文件一般都比较小,且在刷回磁盘的过程中是顺序io
,相比于随机io
来说,性能更好。
redolog结构
redolog
由两部分组成:
- 一个是内存中的日志缓存
redo log buffer
- 一个是磁盘中的日志文件
redo log file
当每次对数据记录进行修改的时候,都会将这些修改内容先写入redo log buffer
中,后续等待合适的时机将内存中的修改刷回到redo log file
中。这种先写日志,再写磁盘的技术就是WAL(Write-Ahead Logging)
技术。需要注意的是redolog
刷回磁盘的时机,它比数据页先刷回磁盘。那么哪些场景会涉及到redolog
的使用呢,主要分两类:
- 索引
undo
页面的修改。
redolog的整体流程
如图所示,当对数据记录进行修改时,redolog
的流程如下:
- 若数据已在内存中则直接进行修改,否则先将数据从磁盘加载到内存中;
- 修改完成之后,生成一条
redolog
,将这条redolog
写入redo log buffer
中,记录的是修改之后的值; - 根据选定的策略,将
redo log file
中的内容刷回到redo log file
中;
redolog 刷回 redo log file的策略
在计算机操作系统中,用户空间的数据一般无法直接写入到磁盘中,中间必须先经过操作系统内核空间缓冲区,即os buffer
。因此,redo log buffer
写入redo log file
实际上是先写入os buffer
中,再通过系统调用fsync()
刷回到磁盘中,过程如下:
在my.ini
配置文件中,可以通过innodb_flush_log_at_trx_commit
参数来配置redo log buffer
如何刷回redo log file
的策略。
- 0:事务提交后不会立即将
redo log buffer
中的日志写入到os buffer
,而是每秒将redo log buffer
写入到os buffer
中,再调用fsync()
写入到redo log file
中。当MySQL
挂了的话,就会丢失1秒钟的数据
- 1:事务提交后都会将
redo log buffer
中的日志写入os buffer
,再调用fsync
刷到redo log file
中。这样方式即使系统崩溃也不会丢失任何数据,但由于每次事务提交时都要写入磁盘,性能较差
- 2:事务提交后仅仅将
redo log buffer
中的日志写入os buffer
,然后每秒调用fsync()
将os buffer
中的日志写入到redo log file
,如果只是MySQL
挂了,不会出现数据丢失,但是要是机器宕机则会丢失1秒钟的数据
redo log 格式
redolog
采用固定大小,循环写入的格式,当redolog
写满之后,会重新从头开始写。为什么这么设计呢?
redo log存在的意义主要就是降低对数据页刷盘的要求。redolog
记录了数据页上的修改,但是当数据页也刷回到磁盘后,这些记录就失去作用了。因此当MySQL
判断之前的redolog
已经失去作用之后,新数据会将这些失效的数据进行覆盖。那如何判断该不该进行覆盖呢?主要根据两个数据,write position
和check point
,如下所示:
write pos
表示redolog
当前已写入redolog
中的日志序列号LSN(log sequence number)
。当数据页也已经刷回磁盘之后,会更新redo log file
中的LSN
,表示到这个LSN
之前的数据已经落盘。在redolog
中还有一个特殊的LSN
,就是check point
。
-
write pos
到check point
之间的部分是redolog
空余的部分,用于记录新的记录 -
check point
到write pos
之间是redolog
已经记录的数据页修改部分,但此时数据页还未刷回磁盘的部分 -
当
write pos
追上check point
时,会先推动check point
向前移动,空出位置再记录新的日志
启动innodb
的时候,不管上次是正常关闭还是异常关闭,总是会进行恢复操作。恢复时,会先检查MySQL
内存中加载进来的数据页中的LSN
,如果这个LSN
小于redolog
中的write pos
位置,说明在redolog
上记录着数据页上尚未完成的操作,接着就会从最近的一个check point
出发,开始同步数据。
那有没有可能数据页中的LSN
大于redolog
中的LSN
呢?答案是当然可能。出现这种情况时,这时超出redolog
的部分将不会重做,因为这本身就表示已经做过的事情,无需再重做。
redolog与binlog区别
redolog | binlog | |
---|---|---|
文件大小 | redolog 的大小是固定的 |
binlog 可通过参数调整binlog 文件的大小 |
实现方式 | redolog 是InnoDB 引擎独有的 |
binlog 是Server 层实现的,所有引擎都可以使用 binlog 日志 |
记录方式 | redolog 采用循环写的方式记录,当写到结尾时,会回到开头循环写日志 |
binlog 通过追加的方式记录,当写满时,会新生成一个文件继续写 |
适用场景 | redo log 适用于崩溃恢复(crash-safe ) |
binlog 适用于主从复制和数据恢复 |
由上面的对比可知,binlog
日志只适合用于归档,只依靠binlog
是没有crash-safe
能力的。但只有redo log
也不行,因为redo log
是InnoDB
特有的,且redolog
会被覆盖掉。因此需要binlog
和redo log
二者同时记录,才能保证当数据库发生宕机重启时,数据不会丢失。
两阶段提交
上面简单介绍了redolog
和binlog
,在对数据进行修改时,他们都会对这些修改进行保存落地,只是一个是物理日志,一个是逻辑日志。那他俩具体在修改过程中是如何执行的呢?
假设现在有一条update
语句要执行,update from table_name set c=c+1 where id=2
,执行流程如下:
- 先定位到
id=2
这一条记录 - 执行器拿到引擎给的行数据,把这个值加上1,得到新的一行数据,再调用引擎接口写入这行新数据
- 引擎将这行新数据更新到内存中,同时将这个更新操作记录到
redolog
里面,此时redolog
处于prepare
状态。然后告知执行器执行完成了,随时可以提交事务; - 执行器生成这个操作的
binlog
,并把binlog
写入磁盘; - 执行器调用引擎的提交事务接口,引擎把刚刚写入的
redolog
修改成提交(commit
)状态,更新完成;
示意图如下所示:
这种将redolog
的写入拆分成prepare
和commit
两个步骤的过程称之为两阶段提交。
redolog
和binlog
都可以用于表示事务的提交状态,而两阶段提交就是让这两个状态保持逻辑上的一致。如果不使用两阶段提交,而是先写其中一个再写另外一个可能会带来一些问题。
此时还是使用update
来举例。假设当前id=2
,有一个字段c=0
,分别分析以下情况:
先写redolog
再写binlog
假设先写redolog
,当redolog
写完,但是binlog
还未写完的时候,此时MySQL
突然出现异常导致重启。由于之前redolog
已经写完,系统重启后,修改的记录仍然存在,所以恢复后这一行c
的值是 1。但由于系统重启,binlog
中并未有这条记录。之后备份日志的时候,存起来的binlog
里面就没有这条语句。然后你会发现,如果需要用这个binlog
来恢复临时库的话,由于这个更新语句的binlog
丢失,这个临时库就会少了这一次更新,恢复出来的这一行c
的值就是 0,与原库的值不同。
先写binlog再写redolog
假如先写binlog
,然后写redolog
的时候系统重启。重启之后,redolog
中没有对c
进行修改的记录,此时c
的值还是0。但是 binlog
里面已经记录了把c
从0改成1这个日志。所以,在之后用binlog
来恢复的时候就多了一个事务出来,恢复出来的这一行c
的值就是 1,与原库的值不同。
因此,如果是先写其中一个日志再写另一个日志的话,就会出现数据库的状态与使用binlog
恢复出来的库的状态不一致的情况。
undolog
undolog
主要用来记录某条行记录被修改之前的状态,记录的是修改前的数据,这样的话,当事务进行回滚时,就可以通过undolog
将记录恢复到事务开始前的样子。事务的原子性和持久性也是依靠undolog
来实现的。
undo log
主要记录了数据的逻辑变化,
-
一条
INSERT
语句,undolog
就会生成对应一条DELETE
语句; -
UPDATE
语句,undolog
就会生成与之相反逻辑的一条语句,这样在发生错误时,就能回滚到事务之前的数据状态 -
在进行数据恢复的时候,与
binlog
,redolog
结合使用,保证了数据恢复的正确性
undolog
的大致流程如下:
- 在事务开始之前将修改前的版本写入到
undolog
中; - 开始进行修改,将修改过的数据保存到内存当中;
- 将
undolog
持久化到磁盘当中; - 将数据页刷回到磁盘当中;
- 事务提交;
这里举个具体的例子:
需要注意的是,与redolog
一样,undolog
也是要先于数据页刷回到磁盘当中。在恢复数据时,如果undolog
是完整的,就可以根据undolog
来回滚事务。
与redolog的合作
在一个事务当中,可能会对同一条数据进行多次修改,如果每次修改都将之前的数据记录到undolog
中,就会导致undolog
日志量太大,于是redolog
华丽登场。
在一个事务当中,如果是对同一条记录进行修改,undolog
只会记录事务开始前的原始记录,当再次对这条记录进行修改时,redolog
会记录后续的变化。在数据恢复时,redolog
完成前滚,undolog
完成回滚,二者相互协调完成数据的恢复。过程如下所示:
其他作用
还有一个功能就是MVCC
多版本控制链了,这个请参考这篇文章,MVCC 多版本控制链。
总结
binlog
,redolog
和undolog
是MySQL
中最重要的三个日志,在进行数据恢复时,三者协调合作,保证数据恢复的正确性。关于这三个日志的知识点总结如下: