Mysql的日志系统是Mysql保证无论何时崩溃数据都不会丢失的关键
众所周知Mysql是持久化的数据库, 所有的数据都是持久化到硬盘中的, 保证数据不会丢失
Mysql保证数据不会丢失是从以下两个方面来体现的
MySQL保证以上两个点的关键就是通过 undo log, redo log 和 binlog 这三个日志来实现的, 接下来将逐一介绍
undo log是Mysql的回滚日志, 存储的是老版本的数据
存储老版本的数据
用于在事务执行失败的时候回滚到事务开始前的版本
undo log 有两种类型
对于 insert 命令, undo log 记录的是新增的记录的主键, 在回滚的时候根据 undo log 中的主键去删除对应的记录即可
对于 update / delete 命令, undo log 记录的是被修改的记录的旧数据
Mysql中的每一行数据都有一个最新修改当前数据行的事务id
和回滚指针
这两个字段, 当对数据行进行修改之后, undo log指针就会指向旧的这一行数据, 而新生成的这一行数据的回滚指针
就会指向undo log指针当前指向的旧数据行
undo log是用于保证事务在未提交的时候可以顺利回滚到事务开始前的状态, 当事务提交之后undo log就失去作用了, 就需要被删除
undo log是交由Mysql中的 Purage 线程来负责删除的, purage会定期检查undo log中的deleted_bit 标志, 这个标志会在事务提交后被设置为true, purage 线程发现为true的记录就会负责将其删除
redo log是Mysql的物理日志, 负责记录某个数据页执行了什么样的操作
对x表的y页z偏移做了a更新
持久化数据到磁盘是随机IO过程, 所以Mysql选择将数据缓存起来, 等待一个合适的时机将数据一次性写入磁盘, 减少IO
但是数据缓存在内存中有丢失的风险, 所以Mysql选择将redo log持久化
redo log是顺序写, 持久化的效率比随机写的效率要高, 并且redo log记录了数据的变化情况, 只要redo log在就可以保证在Mysql重启后恢复数据
在InnoDB中, redo log是一个固定大小的类似循环队列的存在, 每次写入都从后面write pos
的位置, 在持久化数据的时候就移动check point
往前读取
undo log记录的是事务执行过程中旧数据的状态, redo log记录的是数据更新之后的状态
redo log其实保障的是事务的持久性和一致性,而undo log则保障了事务的原子性
binlog是Mysql server层实现的日志, 是所有引擎通用的
binlog记录的是mysql原始的语句逻辑, 并且是采用追加写入的形式记录的, 所以可以用于恢复mysql在任意时刻的数据库数据状态
先写日志到磁盘中, 再写数据到磁盘中
Mysql的写操作不是立刻写入到磁盘中的, 而是先写日志, 保证redo log和binlog都持久化到磁盘中再由后台线程选择时机将数据持久化到硬盘的
因为刷脏页是一个随机读写的过程, 持久化到磁盘中的速度肯定没有redo log | binlog这些顺序写的速度快, 所以选择先在内存中对数据进行修改, 再后期选择时机异步持久化到磁盘中
所以在脏页还未刷入磁盘中的这段时间就由redo log | binlog来保证数据的持久化, 防止断电重启等情况内存中的数据丢失
当脏页满的时候需要将脏页写入到磁盘再淘汰, 为何不全部淘汰掉下次使用的时候再通过redo log来恢复呢
从性能方面考虑的, 如果每次从磁盘中读取数据到内存都需要和redo log比对更新, 效率很低
MySQL刷脏页写入到磁盘保证了数据页只要在内存中, 就肯定是当前最新的数据可以返回
如果内存中没有数据只要从磁盘中读取肯定能得到最新的正确数据, 而不用再去同redo log进行比对
binlog和redo log都是将日志写入划分为三个过程 写入cache, write和sync
在事务执行过程中会将binlog和redo log写入到对应分配的缓存中, 以便在事务提交的时候一次性写入到磁盘中
在事务提交的时候会先进行write将数据写入到操作系统的页缓存中, 此时数据还未真正写入文件, 但是已经是交由操作系统的缓存来保管了, 如果此时Mysql进程崩溃这部分写入的数据也不会丢失, 操作系统的内核线程会负责将这部分缓存中的数据写入磁盘
最后就是mysql手动调用sync将写入在页缓存中的数据持久化到硬盘, 写入完成后数据就是持久化成功了
最后的write和sync步骤mysql提供了对应的参数来控制写入策略
redo log是通过innodb_flush_log_at_trx_commit
来控制的
binlog是通过参数sync_binlog
来控制的
什么是两阶段日志提交
将redo log日志提交的过程分为prepare和commit这两个阶段, binlog日志提交在这两个阶段中间
事务提交时redo log先提交后进入prepare状态, 然后binlog提交完成后redo log才能将日志的状态修改为commit已提交
为什么需要两阶段日志提交
和InnoDB引擎的回滚机制有关, InnoDB的redo log提交了事务就无法回滚了, 如果在redo log提交后binlog写入失败的话就会出现两份不统一的情况
如果此时数据库异常重启的话要依据那一份来恢复数据就值得思考了, 所以才需要两阶段日志提交
假设现在在时刻A数据库崩溃的话, 因为binlog还未写入, redo log还未提交, 所以重启后事务会回滚, 两份日志依旧是统一状态
如果是时间段B的话, 就需要对redo log的提交标志进行判断了, 在查询redo log中是否有commit提交标志, 如果有的话事务没有问题, 直接提交
如果redo log中没有对应事务的提交标志的话会对binlog进行检查
这里可以发现两阶段日志提交中发生了崩溃是依据binlog来进行标准判断的, 原因是因为主从复制是依据binlog来进行的
如果对两份日志都需要检查完整性的话, 主库挂掉切换到从库的时间会变长, 以binlog为基准的话主库挂了直接拿着binlog去从库恢复数据即可, 无需检查redo log的完整性
此外binlog是Mysql Server层的通用日志, 这也是选择binlog作为基准的原因
两阶段日志提交的缺点
磁盘IO次数高
锁竞争激烈
组提交机制的作用
当有躲过事务提交的时候, 将多个事务的日志合并在一起去写入, 减少磁盘IO操作
组提交机制的实现
组提交机制将commit过程拆分成三个过程, 对每个过程都维护了一个队列, 并且通过锁来保证事务的写入顺序
当队列为空的时候第一个进入队列的事务会成为后续进入的事务的领导者, 带领后续事务完成接下来的阶段操作
阶段一 : flush阶段
: 多个事务按进入的顺序将binlog从cache中写入文件 (不刷盘)
第一个进入flush阶段的事务会作为领导者领导后面进入的事务
领导者事务会带领所有的事务对 redo log 进行一次 write + fsync, 也就是将redo log 写入磁盘, 完成redo log 的propare阶段
如果在这个阶段Mysql崩溃了, 会在重启后回滚这组事务
阶段二 : sync
: 对binlog文件做fsync操作 (将多个事务的binlog合并一起刷盘)
在flush阶段将binlog写入到binlog文件后, 会等待一段时间再进行刷盘, 目的是组合更多事务的binlog一起刷盘减少消耗
等待会有时间限制和最大事务限制, 满足其中一个条件就会立刻对binlog进行刷盘
sync阶段主要负责binlog的组提交, 如果当前阶段Mysql崩溃的话, 在重启后可以通过redo log的刷盘记录继续完成事务提交
阶段三 : commit
: 对各个事务做InnoDB的commit操作