redo log、undo log、bin log详解
目录
引言
我们知道一条select语句的执行过程如下图所示
那么,执行一条update语句期间发生了什么?
执行步骤和查询语句是类似的
- 客户端先通过连接器建立连接,连接器会判断用户身份
- update语句不需要经过查询缓存,但是表上有更新语句的话,是会把整个表的查询缓存清空的,所以查询缓存在MySQL 8.0 被移除了。
- 解析器会通过词法分析识别出关键字update,表名等。构建出语法树,接下来会做语法分析,判断输入的语句是否符合MySQL语法。
- 预处理器会判断表和字段是否存在。
- 优化器确定执行计划,如果where条件中id是主键索引,会决定使用id这个索引
- 执行器负责具体执行,找到此行,然后更新。
但是update还会涉及到几个重要日志文件:redo log、bin log、undo log。
1. redo log
1.1 为什么需要redo log ?
update包含两步操作,先查询到对应的行记录,再根据条件进行更新操作。如果没有redo log的话,MySQL每次的update操作都要更新磁盘文件,整个过程的I/O成本和查询成本都很高。
1.2 redo log的作用?
- 为了防止断电导致的数据丢失的问题,当有一条记录需要更新的时候,
InnoDB
引擎就会先更新内存(同时标记为脏页),然后将本次对这个页的修改以 redo log的形式记录下来,这个时候更新就算完成了。 - 将写操作从随机写变成了顺序写
后续,InnoDB
引擎会在适当的时候,由后台线程将缓存在Buffer Pool的脏页刷新到磁盘里,这就是**WAL(Write-Ahead Logging)
**技术。
✨**
WAL
**技术指的是,MySQL的写操作并不是立刻写到磁盘上,而是先写日志,然后在合适的时间再写到磁盘上。
1.3 什么是redo log?
redo log是物理日志,记录了某个数据页做了什么修改,比如对XXX表空间中的YYY数据页ZZZ偏移量的地方做了AAA更新。
每当执行一个事务就会产生一条或者多条这样的物理日志。
在事务提交时,只要先将redo log持久化到磁盘即可,可以不需要等到将缓存在Buffer Pool里的脏页数据持久化到磁盘。
当系统崩溃时,虽然脏页数据没有持久化,但是redo log已经持久化,在MySQL重启后,可以根据redo log的内容,将所有数据恢复到最新的状态。
1.4 redo log要写入到磁盘,数据也要写磁盘,为什么要多次一举
写入redo log的方式使用了追加操作,所以操作顺序是顺序写,写入数据是随机写,需要先找到写入位置,然后才写到磁盘。
✨磁盘的顺序写比随机写高效的多,因此 redo log写入磁盘的开销更小。
WAL
技术的另外一个优点:MySQL的写操作从磁盘的随机写变成了顺序写。
1.5 产生的 redo log是直接写入磁盘的吗?
不是的
在执行一个事务的过程中,产生的 redo log也不是直接写入磁盘的,因为这样会产生大量的I/O操作,而且磁盘的运行速度远慢于内存。
redo log也有自己的缓存——redo log buffer,每当产生一条redo log时,会先写入到redo log buffer,后续再持久化到磁盘
redo log buffer默认大小16MB,可以通过 **innodb_log_Buffer_size
**参数动态的调整大小,增大它的大小可以让MySQL处理大事务的时候不必写入磁盘,进而提升写IO性能。
1.6 redo log什么时候刷盘?
redo log buffer中的redo log还是在内存中,什么时候刷新到磁盘?
- MySQL正常关闭时。
- 当redo log buffer中记录写入量大于其内存空间的一半的时候,会触发落盘。
InnoDB
的后台线程每隔1s,将redo log buffer持久化到磁盘。- 每次事务提交时都将缓存在 redo log buffer 里的 redo log 直接持久化到磁盘。
1.7 redo log文件写满了怎么办
默认情况下,**InnoDB
**存储引擎有1个重做日志文件组,由2个redo log文件组成,这两个redo日志的文件名是 **ib_logfile0
**和 ib_logfile1
在重做日志组中,每个 redo log File 的大小是固定且一致的,假设每个 redo log File 设置的上限是 1 GB,那么总共就可以记录 2GB 的操作。
重做日志文件组是以循环写的方式工作的,从头开始写,写到末尾就又回到开头,相当于一个环形。
如果随着系统运行,Buffer Pool 的脏页刷新到了磁盘中,那么 redo log 对应的记录也就没用了,这时候我们擦除这些旧记录,以腾出空间记录新的更新操作。
redo log 是循环写的方式,相当于一个环形,InnoDB
用 write pos
表示 redo log 当前记录写到的位置,用 **checkpoint **表示当前要擦除的位置,如下图:
如果**write pos
追上了checkpoint
,就意味着redo log文件满了,这时MySQL不能再执行新的更新操作,也就是MySQL会被阻塞 **,此时会停下将Buffer Pool中的脏页刷新到磁盘中,然后标记redo log哪些记录可以被擦除,接着对旧的redo log记录进行擦除,等擦除完了旧记录腾出了空间,checkpoint就会往后移动。
✨因此针对并发量大的系统,适当设置redo log的文件大小非常重要。
2. binlog
2.1 binlog
是什么
上面提到的redo log是执行引擎层的log文件,binlog
是Server层面的log文件,也是就是所有执行引擎都有binlog
。
2.2 为什么InnoDB
有一份log文件,MySQL还需要一份log文件呢
这个跟MySQL的时间线有关系
最开始MySQL里并没有InnoDB
引擎,MySQL自带的是**MyISAM
,但是MyISAM
没有crash-safe的能力,binlog
**日志只能用于归档。
而 InnoDB
是另一个公司以插件形式引入 MySQL 的,既然只依靠 binlog
是没有 crash-safe 能力的,所以 InnoDB
** 使用 redo log 来实现 crash-safe 能力。**
2.3 redo log
和binlog
有什么区别?
- 适用对象不同
binlog
是MySQL的server层实现的日志,所有的存储引擎都可以使用- redo log是**
InnoDB
**存储引擎实现的日志
- 文件格式不同
- 写入方式不同
binlog
是追加写,写满一个文件,就创建一个新的文件继续写,不会覆盖以前的日志,保存的是全量的日志。- redo log是循环写,日志空间大小是固定的,全部写满就从头开始,保存未被刷盘的脏页日志
- 用途不同
binlog
用于备份恢复、主从复制- redo log用于掉电等故障恢复
- redo log是物理日志,记录的是 在某个数据页做了什么修改,而
binlog
是逻辑日志,记录的是语句的原始逻辑。
2.4 如果不小心整个数据库的数据被删除了,能使用 redo log文件恢复数据吗?
不能,只能使用**binlog
,因为binlog
中保存的是全量**的日志,而redo log不是。
2.5 主从复制是怎么实现的?
MySQL的主从复制依赖于binlog
,binlog
记录数据库中的所有变化并以二进制的形式保存在磁盘上,复制的过程就是将**binlog
**中的数据从主库传输到从库上。
✨这个过程一般是异步的
MySQL集群的主从复制过程可以整理成3个阶段
- 写入**
binlog
:主库写入binlog
**日志,提交事务并更新本地存储数据。 - 同步**
binlog
:把binlog
复制到所有从库上,每个从库把binlog
写到暂存日志中 **。 - 回放**
binlog
**:从库回放binlog
,并更新存储引擎中的数据。
具体详细的过程如下
- MySQL主库在收到客户端提交事务的请求之后,会先写入
binlog
,再提交事务,更新存储引擎中的数据,事务提交完成后,返回给客户端操作成功的响应。 - 从库会创建一个专门的I/O线程,连接主库的log dump线程,来接收主库的
binlog
日志,再把binlog
信息写入relay log的中继日志中,然后返回给主库复制成功的响应。 - 从库会创建一个用于回放
binlog
的线程,去读relay log中继日志,然后回访binlog
更新存储引擎中的数据,最终实现主从的数据一致性。
在完成主从复制后,你就可以在写数据时只写主库,在读数据时只读从库,这样即使写请求会锁表或者锁记录,也不会影响读请求的执行。
Redis的主从复制过程是如何进行的呢?
2.6 从库是否是越多越好?
答案是否定的。
因为从库数量增加,从库连接上来的I/O线程也会更多,主库需要创建同样多的log dump线程来处理复制的请求,对主库资源消耗比较高,同时还受限于主库的网络带宽。
2.7 binlog
什么时候刷盘?
事务执行过程中,先把日志写到**binlog cache
(Server层的cache) **,事务提交的时候,再把binlog cache
写到binlog
文件中。
✨一个事务的
binlog
是不能被拆开的
无论这个事务有多大,也要保证一次性写入,因为一个线程同时只能由一个事务在执行,所以每当执行一个begin/start transaction的时候,就会默认提交上一个事务,这样**如果一个事务的binlog
**被拆开的时候,在备库执行的时候就会被当作多个事务分段执行,这样破坏了原子性,是有问题的。
2.8 什么时候binlog cache
会写入到binlog
文件
在事务提交的时候
✨虽然每个线程有自己
binlog cache
,但是最终都写到同一个**binlog
**文件:
- 图中的 write,指的就是指把日志写入到 binlog 文件,但是并没有把数据持久化到磁盘,因为数据还缓存在文件系统的 page cache 里,write 的写入速度还是比较快的,因为不涉及磁盘 I/O。
- 图中的 fsync,才是将数据持久化到磁盘的操作,这里就会涉及磁盘 I/O,所以频繁的 fsync 会导致磁盘的 I/O 升高。
3. Buffer Pool
3.1 为什么需要Buffer Pool
Innodb
存储引擎设计了一个缓存池,来提高数据库的读写性能。
更新一条记录的时候,先缓存起来,这样下次有查询语句命中了这条记录,可以直接读取缓存中的记录,就不需要从磁盘中获取数据了。
3.2 Buffer Pool会缓存什么
Buffer Pool 除了缓存「索引页」和「数据页」,还包括了 Undo 页,插入缓存、自适应哈希索引、锁信息等等。
MySQL 刚启动的时候,你会观察到使用的虚拟内存空间很大,而使用到的物理内存空间却很小,这是因为只有这些虚拟内存被访问后,操作系统才会触发缺页中断,申请物理内存,接着将虚拟地址和物理地址建立映射关系。
4. undo log
4.1 为什么需要undo log ?
在执行一条 增删改 语句的时候,虽然没有输入begin开始事务和commit提交事务,但是MySQL会隐式开启事务来执行增删改语句的。
执行一条语句是否自动提交事务,是由**autocommit
**参数决定的,默认是开启的。
✨undo log(回滚日志),保证了事务ACID特性中的原子性
undo log是一种用于撤销回退的日志。在事务没提交之前,MySQL会先记录更新前的数据到undo log日志文件里面,当事务回滚时,可以利用undo log来进行回滚。
每当 InnoDB
引擎对一条记录进行操作(修改、删除、新增)时,要把回滚时需要的信息都记录到 undo log 里
不同的操作,需要记录的内容也是不同的,所以不同类型的操作(修改、删除、新增)产生的 undo log 的格式也是不同的。
4.2 版本链
一条记录的每一次更新操作产生的undo log格式都有一个**roll_pointer
指针和一个trx_id
**事务id。
- 通过**
trx_id
**可以知道该记录是被哪个事务修改的 - 通过**
roll_pointer
指针可以将这些undo log串成一个链表,这个链表就被称为版本链 **。
4.3 多版本并发控制(MVCC)
undo log还有一个作用,通过**ReadView + undo log
**实现多版本并发控制
对于读已提交和可重复度隔离级别的事务来说,它们的快照读(普通的select语句)是通过**ReadView + undo log
**来实现的,区别在于创建Read View 的时机不同。
- 读已提交隔离级别是在每个select都会生成一个新的Read View,也就意味着,事务期间的多次读取同一条数据,前后两次读的数据可能会出现不一致,因为可能这期间另外一个事务修改了该记录,并提交了事务。
- 可重复读隔离级别是启动事务时生成一个Read View,然后整个事务期间都在用这个Read View,这样就保证了事务期间读到的数据都是事务启动前的记录。
这两个隔离级别的实现是通过事务的Read View里的字段和undo log记录中的两个隐藏列(trx_id
和roll_pointer
)的对比,如果不满足可见行,就会顺着undo log版本链里找到满足其可见性的记录,从而控制并发事务访问同一个记录时的行为,就叫MVCC
。
4.4 undo log的两大作用
- 实现事务的回滚,保障事务的原子性。
- 实现**
MVCC
**的关键因素之一。
5. 两阶段提交
5.1 为什么需要两阶段提交?
事务提交后,redo log
和binlog
都要持久化到磁盘,但这两个是独立的逻辑,可能出现版成功的状态,这样就造成两份日志之间的逻辑不一致。
- 如果在redo log刷入磁盘之后,MySQL突然宕机了,而binlog还没有来得及写入。
- 如果在将binlog刷入磁盘之后,MySQL突然宕机了,而redo log还没来得及写入。也会导致主从不一致。
5.2 两阶段提交的过程
✨将redo log的写入拆成了两个步骤:prepare和commit,中间再穿插写入
binlog
。
5.3 两阶段提交有什么问题
虽然保证了两个日志文件的数据一致性,但性能很差。
- 磁盘I/O次数高
- 锁竞争激烈。 两阶段提交虽然能保证单事务的两个日志的内容一致,但在多事务的情况下,却不能保证两者的提交顺序一致,因此,在两阶段提交的流程基础上,还需要加一个锁来保证提交的原子性。