MySQL三大日志(redo log,binlog,undo log)
在文章开始前先抛出一些概念和问题,对已经接触过三大日志的读者可以做一个自测,如果不会那么本文将会详解这些概念助你理解,如果都会,那你可以忽略本文了(开个玩笑,可以看看查漏补缺)
常见问题:
三大日志的作用是什么?
Redo Log的三种刷盘策略(0、1、2)有何区别?
MySQL挂机和系统宕机有何区别?为什么有的场景数据会丢失?
Redo Log事务执行到一半被后台线程强制刷盘怎么办?
Redo Log刷盘和数据页(脏页)刷盘的区别是什么?
WAL是什么?为什么需要先写日志再修改数据页,而不是直接刷盘数据页?
先从三大日志的作用与功能入手:
- redo log(重做日志):
redo log
(重做日志)是 InnoDB 存储引擎独有的,它让 MySQL 拥有了崩溃恢复能力;
MySQL 实例挂了或宕机了,重启时,InnoDB 存储引擎会使用.idb
文件 和redo log
恢复数据(.ibd文件是InnoDB存储引擎层的物理存储文件,也就是存储实际数据页的地方)
redo log
它是物理日志,记录内容是“在某个数据页上做了什么修改”
(下图是redo log的流程)
流程解析:当查询数据时,若Buffer Pool中没有,就从磁盘中找,并且放入Buffer Pool,后续的查询和更新都在Buffer Pool中,然后会把“在某个数据页上做了什么修改”记录到重做日志缓存(redo log buffer
),最后定期刷盘到redo log文件中
可以看到步骤2 3都是随着事务进行而进行的,那么什么时候执行第四步刷盘呢?
redo log
刷盘有多种情况(比如事务提交,log buffer 空间不足,Checkpoint检查等情况,但是为了降低理解难度,我们先只讲最主要的事务提交的刷盘策略)
我们主要学习的就是事务提交时的刷盘策略,由参数innodb_flush_log_at_trx_commit
控制,共有三种刷盘策略,对应参数的值为 0,1,2 我们按照优先级(持久化能力从低到高,性能从高到低)来讲:
提前对下面的图提前做两个常识性的补充:
一. 首先明确我们的redo log
写入流程还有个BufferPool
,但是下面三张图省略了,我们只需要知道当我们执行语句时Buffer Pool
和redo log buffer
是同步进行的即可;
二. 其次我们把MySQL后台线程的执行流程分成两块,把redo log buffer
写入page cache
称为write
,从page cache
刷盘到磁盘称为fsync
,这两个函数熟悉操作系统的都应该知道,我们对不熟悉的读者进行一个补充
0:
我们执行事务,正常在Buffer Pool
和redo log buffer
中更新数据和记录 (这个流程三种策略都是一样的,后面不赘述)
然后MySQL的后台线程每秒都会调用write
和fsync
,把redo log buffer
中的内容先写入page cache
,再刷盘到磁盘
这种情况下不论是 MySQL挂了 还是 设备宕机都可能丢失1秒的数据
常识性补充:
MySQL实例挂机会导致redo log buffer中数据丢失;
Page Cache是操作系统的内核缓冲区,属于操作系统层面,系统宕机和断电均会导致Page Cache中数据丢失
2:
这里相比0的情况,区别就是每次提交事务的时候会额外进行一个write
操作,把redo log buffer
写到page cache
,其他操作一样
这种情况下 MySQL挂了不丢失数据 还是 设备宕机可能丢失1秒的数据
1:
这里相比0的情况,区别就是每次提交事务的时候会额外进行write
和fsync
,直接把redo log buffer
写入page cache
并且刷盘
相比前两种情况,这里的后台线程每秒扫描的时候,如果正好提交事务,刚刷过盘,那么后台线程是不会进行刷盘的,即后台线程的 fsync()
仅在 Page Cache 中有新日志时触发
这种方法只要事务提交成功,redo log
记录就一定在硬盘里,不会有任何数据丢失,也是MySQL默认的刷盘策略,保证事务持久性
为什么参数为2的时候MySQL挂机不会丢失数据,而系统宕机会丢失数据呢?
因为这种策略下已经提交的事务直接写入系统内核缓存,MySQL挂了不会导致内核缓存清除
而Linux系统中5秒检查一次脏页,30秒未刷盘的脏页会被操作系统刷盘,或者等待MySQL重启刷盘,所以之后还是可以持久化数据页
但是操作系统宕机那系统内核缓存自然清空,数据丢失
而参数为1时,每次提交的事务都会被刷盘,所以不会丢失任何数据
日志文件组:
硬盘上记录redo log的文件一般是以日志文件组的形式出现的,比如可以配置为一组4
个文件,每个文件的大小是 1GB
,整个 redo log 日志文件组可以记录4G
的内容
它采用的是环形数组形式,从头开始写,写到末尾又回到头循环写
在这个日志文件组中还有两个重要的属性,分别是write pos
、checkpoint
分别代表当前记录位置和当前要擦除的位置,后者也可以说是最后一次成功刷盘的脏页对应的日志点
- 每次进行
redo log
刷盘,write pos
就会后移 - 每次MySQL加载日志文件组恢复数据,就会清空已经加载了的日志记录,即把
checkpoint
后移 checkpoin
推进机制:正常情况下Innodb
存储引擎通过配置参数控制脏页刷盘以小批量、异步方式进行刷盘,推进checkpoint
高负载场景可能触发同步刷盘,导致性能波动
那这时候就有同学要懵比了,你说的第一点我还看的懂,redo log
写入一点write pos
就后移一点嘛,很好理解,但是你这个checkpoint
后移和推进是个什么意思?
重点来了!!!(新手最容易搞混的地方)
如果你产生了这个问题,那么恭喜你,成功地没搞清楚redo log
刷盘和数据页(脏页)刷盘的概念(虽然作者刚学的时候也没看明白,一脸懵逼)
这里checkpoint
所说【最后一次成功刷盘的脏页的日志点】,是指我们redo log
日志【刷盘】到数据页
我们前面所说的 “持久化,刷盘” 只是说短期内提交的的事务 持久化刷盘 到 redo log
日志文件 里,实际存储数据页的地方是前文提到的.ibd文件
我们可以认为日志文件只是作为一个中转站把记录的数据(或者说是脏页)【刷盘】到实际数据页中
这里就涉及到了一个概念:WAL(Write-Ahead Logging),对数据页修改之前必须先写入日志,而MySQL采用的就是这种技术,先把修改写入日志文件,再根据日志文件修改数据页
最后一个简单小问题:我们上面已经区分了 redo log日志刷盘 和 数据页刷盘的区别了,那么为什么在MySQL中我们要先修改日志进而修改数据页,而不是直接一步到位刷盘数据页呢?
答:
因为日志是顺序写,并且只包含数据修改的操作,比如数据页号,磁盘偏移量,更新值等少量数据
而修改的数据页是随机写的,并且完整数据页包含很多未涉及修改的无效数据,性能差
-
下面我们要讲
binlog
了,先提前写个前置知识点,但是这里不是重点,略作了解即可,也可以先跳过,等产生疑问了再回来
MySQL Server 层:连接层,SQL执行层
InnoDB 存储引擎层:内存架构,磁盘架构,后台线程对比维度 MySQL Server 层 InnoDB 存储引擎层 核心职责 处理 SQL 解析、优化、执行等跨存储引擎的通用功能 负责数据存储、索引管理、事务处理(ACID)、并发控制等底层操作 关键组件 连接器、查询缓存、解析器、优化器、执行器 缓冲池(Buffer Pool)、Redo Log、Undo Log、Change Buffer、自适应哈希索引等 数据存储方式 不直接存储数据,仅通过存储引擎接口操作数据 数据以页(16KB)为单位存储在 .ibd 文件(独立表空间)或共享表空间中,支持 B+ 树索引结构 事务支持 不直接处理事务,依赖存储引擎实现 支持事务的原子性(Undo Log)、持久性(Redo Log)、隔离性(MVCC) 性能优化重点 SQL 语句优化、查询缓存管理 内存缓冲池管理、日志顺序写入、脏页异步刷盘、索引合并等 -
binlog(归档日志):MySQL 数据库的数据备份、主备、主主、主从都离不开 binlog,需要依靠 binlog 来同步数据,保证数据一致性
binlog 是逻辑日志,记录内容是语句的原始逻辑,类似于“给 ID=2 这一行的 c 字段加 1”,属于MySQL Server
层
binlog 日志有三种格式,可以通过
binlog_format
参数指定:statement
,row
,mixed
:
statement
:指定statement
,记录的内容是SQL
语句原文,比如执行一条update T set update_time=now() where id=1
,但是有个问题,update_time=now()
这里会获取当前系统时间,直接执行会导致与原库的数据不一致
row
:为了解决这种问题,我们需要指定为row
,记录的内容不再是简单的SQL
语句了,还包含操作的具体数据,row
格式记录的内容看不到详细信息,要通过mysqlbinlog
工具解析出来,这种方式可靠性高,但是比较占用空间,恢复与同步时会更消耗 IO 资源,影响执行速度
mixed
:所以就有了一种折中的方案,指定为mixed
,记录的内容是前两者的混合。
MySQL 会判断这条SQL
语句是否可能引起数据不一致,如果是,就用row
格式,否则就用statement
格式
binlog的写入时机,如图
write
和fsync
的时机,可以由参数sync_binlog
控制,默认是1
为0
的时候,表示每次提交事务都只write
,由系统自行判断什么时候执行fsync
为1
则表示每次提交事务都fsync
,也可以设置为N
,表示提交N
个事务才fsync
-
两阶段提交(2PC):
在执行更新语句过程,会记录redo log
与binlog
两块日志,以基本的事务为单位
redo log
在事务执行过程中可以不断写入(主要是buffer一直是同步写的,有后台线程每秒刷盘,不强调事务的原子性)
binlog
只有在提交事务时才写入(保证原子性),所以redo log
与binlog
的写入时机不一样
从设计初衷上来说:redo log
侧重崩溃恢复效率,需尽早记录修改;binlog
侧重数据一致性,需确保逻辑操作完整两者的差异就会产生问题:如果
redo log
正常写完,而binlog
写入期间发生异常,数据修改失败,那么MySQL主库加载redo log
数据正常,而从库备库加载binlog
数据丢失,导致数据不一致
为了解决两份日志之间的逻辑一致问题,InnoDB 存储引擎使用两阶段提交(2PC)方案:将 redo log 的写入拆成了两个步骤prepare
和commit
,这就是两阶段提交
这样处理就能解决两个日志数据不一致的问题了
一、如果写入binlog
出错无数据,MySQL发现redo log
还是prepare
阶段,且无binlog
记录,则回滚该事务
二、如果redo log
设置commit
出错,未标记成commit
,但是能通过事务id
找到对应的binlog
日志,所以 MySQL 认为是完整的,就会提交事务恢复数据,不会回滚事务
两种情况均可以解决 -
undo log(回滚日志):
实现事务回滚:事务处理过程中,如果出现了错误或者用户执行回滚操作,MySQL 可以利用undo log
中的历史数据将数据恢复到事务开始之前的状态。
实现 MVCC:MVCC 是通过 ReadView + undo log 实现的。undo log 为每条记录保存多份历史数据,MySQL 在执行快照读的时候,会根据事务的 Read View 里的信息,顺着 undo log 的版本链找到满足其可见性的记录MySQL有默认的自动提交机制(Autocommit):MySQL默认启用
autocommit
模式(autocommit=ON
),即每条增删改语句会被视为一个独立的事务。执行成功后,MySQL会自动提交事务;若执行失败,则自动回滚
实现这一机制就是undo log
,它保证了事务的的原子性
undo log
属于逻辑日志,记录的是 SQL 语句,在执行事务的时候会记undo log
日志,并且是记录相反的操作用于回滚,比如执行的是insert
操作,那么undo log
里面的就是delete
操作回想前面的
redo log
操作,我们提到了后台线程每秒自动刷盘,而我们的事务执行的时候又是同步写入redo log buffer
的,也就是说会出现事务未提交就被刷盘的情况,这种情形也是通过undo log
实现回滚的最后引用一下别人对
undo log
刷盘的介绍,本文就到此为止啦~很多人疑问 undo log 是如何刷盘(持久化到磁盘)的?
undo log 和数据页的刷盘策略是一样的,都需要通过 redo log 保证持久化。
buffer pool 中有 undo 页,对 undo 页的修改也都会记录到 redo log。redo log 会每秒刷盘,提交事务时也会刷盘,数据页和 undo 页都是靠这个机制保证持久化的。