MySQL三大日志详解(binlog、undo log、redo log)
MySQL的日志有很多种类,最终要的有三种,binlog、redo log和undo log日志。他们分别是用来主从复制(备份恢复)、故障恢复和事务回滚的。
binlog
binlog是 Server层 的日志,无论存储引擎层使用了什么存储引擎,都可以使用binlog来记录对数据库的更新操作。事务还未提交的时候,这些更新记录会保存在 binlog cache 中,然后事务提交之后,这个binlog cache就会被 追加写 到binlog文件中去。binlog 主要是用来做 主从复制 和 备份恢复 的。
主从复制
主库在提交了一次更新数据库操作的事务时,就会把这次操作记录在binlog中,然后会有一个 log dump 线程,不断地把binlog的数据发送给从库,从库中会有一个 I/O线程 来接收从主库传来的binlog,将它放在从库的 relay log 中保存,并且还会有一个sql线程来读取这个relay log中最新的日志,然后执行它,以此保证了主库和从库的一致性。
同步复制: 主库在写完一次binlog之后,也就是一次事务提交之后,必须要等待所有从库都复制成功并且响应之后,才向客户端返回这次更新操作的结果。这种性能特别差,基本不考虑使用。
异步复制: 主库执行完提交事务之后就立刻返回客户端响应,不会去等待从库的复制,这种方式有个缺点,就是我们一般会在主库宕机的时候使用一个从库来当主库,所以如果主库提交事务之后,binlog还未同步到从库的过程中间宕机了,那么我们切换的从库来当新的主库就会丢失数据。异步复制是mysql默认情况下的主从复制模型。
半同步复制: 这种模型结合了同步复制和异步复制模型,当主库提交一条事务之后,会等待一个从库的同步完成,而不是等待所有从库的同步完成,就会响应客户端。
undo log
我们都知道一个事务在提交之前,这个事务对数据库所做的一切操作是没有被持久化的,如果事务崩溃,那么这个事务崩溃之前所做的操作都需要被回滚,那么mysql是怎么做回滚操作的呢?就是依靠这个undo log。
当每次执行 增、删、改 操作的时候,mysql都会为对应数据生成一条undo log,也就是这个操作之前的该条数据,如果事务崩溃了需要回滚,就会依靠这个undo log来进行回滚操作。
在 插入 一条记录的时候,会把这条记录的主键值记下来,回滚的时候删除这个主键值。
在 删除 一条记录的时候,会把这条记录的内容都记下来,回滚的时候插入会去。
在 修改 一条记录的时候,会把更新的记录的旧值记录下来,回滚的时候更新回旧值。
一条记录的每一次产生的undo log都会有一个 trx_id 和 roll_pointer 字段,分别用来记录时哪个事务修改的,和这条记录的上一次修改的undo log。由roll_pointer串联起来的一条记录就形成了该条记录的 版本链 。
可以通过read view+undo log来实现 MVCC。
redo log
buffer pool 增加了读写效率,但是它是基于内存来暂时保存数据的更新的,那么就会有丢失的风险,redo log就是为了防止这里的数据丢失而出现的。
我们知道我们使用了buffer pool增加读写效率之后,对数据的修改就不是直接落盘的了,而是将修改的数据页标记为脏页,然后在特定时机将脏页刷新到磁盘。每次在更新数据的时候,不仅操作缓存页,还会写一条redo log保存在 redo log buffer中来保存这次的更新,redo log buffer会定时保存到磁盘中去,也会在事务提交的时候持久化到磁盘(根据选择刷盘的策略不同)(redo log数据量小,磁盘I/O就快,并且是追加写,也就是 顺序写 ,而如果直接写入数据就是 随机写 ,所以持久化日志可以快速地持久化数据)。undo log在buffer pool中也有一个缓存的,它的刷盘策略和普通的数据页是一样的,也是要依靠redo log来防止数据丢失。注意redo log是 循环写 ,binlog是 追加写 。
两阶段提交
undo log是依靠redo log来进行持久化保存的,所以它的刷盘成功与否在于redo log是否正常执行了。而redo log和binlog刷盘的时机都有在事务提交的时候刷盘,但是这两个操作是独立的,所以就会有可能出现redo log刷盘成功而binlog失败 或者 binlog成功,redo log刷盘失败的不一致情况。
MySQL为了解决这个两种日志之间的不一致问题,就提出了 两阶段提交 的方法来解决。两阶段提交方法其实是 分布式事务一致性协议 ,它可以保证多个逻辑操作要么全部成功,要么全部失败,不会出现 半成功 的状态。
MySQL在事务提交时,持久化redo log 和 binlog时使用 内部XA事务 ,分两阶段来完成这个事务,这个事务完成之后就代表整体事务的提交成功,redo log和 binlog都持久化完成。内部事务也有内部事务id(XID)。
prepare阶段: 将XID写入redo log,然后redo log事务状态设置为prepare,持久化到磁盘。
commit阶段: 将XID写入到binlog,binlog持久化到磁盘,接着将redo log的prepare状态调整为commit状态,该状态不需要持久化到磁盘,只要binlog 持久化到磁盘,那么redo log无论是哪个状态,都代表它已经被持久化了。
如果在两个阶段之间发生了故障,那么binlog中是没有已持久化了的redo log对应的XID的,那么就会执行回滚,将redo log的prepare状态的数据清除掉。如果binlog中有与redo log中相同的XID,那么无论redo log的状态是prepare还是commit,那么都证明刷盘完成,则提交事务。
所以说,可以说两阶段提交就是固定了两个日志持久化的顺序,然后以binlog写成功为事务提交成功的标识。