本文转载自微信公众号「Java极客技术」,作者鸭血粉丝 。转载本文请联系 Java极客技术公众号。
阿粉这么贴心,肯定给你讲清楚了~
在 MySQL 中,特别是存储引擎使用的是 InnoDB 时,那肯定绕不过去两个概念:redo log (重做日志) 和 binlog (二进制日志)
简单点儿说:redo log 是 InnoDB 存储引擎层方面的日志,所以如果你使用的存储引擎不是 InnoDB 的话,那就根本谈不上 redo log
binlog 是 MySQL Server 层记录的日志,所以不管是用的什么存储引擎,只要是 MySQL 都是会有 binlog 的存在,在做 MySQL 主从复制的时候,利用的就是 binlog
那么,你有疑问嘛?为什么要有 redo log 和 binlog ,只用一个 log 不可以嘛?咱们详细来看看它们都分别做了啥
redo log
为什么要有 redo log ?
我们可以这样想,如果没有 redo log 的话, MySQL 是如何进行工作的 查询还好说,毕竟只是查询一下记录而已,并没有对数据进行更改
那如果是增加和更新操作呢?现在一条 update 语句过来,后面是不是一定会有限定条件,就比如现在要更新一条记录,把 A 的银行卡余额更新到 1k ,那这条语句是不是应该来个限定条件,类似 where userName = 'A',也就是说,一般 update 操作都伴随着查询的操作,得先找到这个人,然后再进行更新操作对吧
如果数据量比较小还好,很快就能找到并且更新完毕
但是如果数据量比较大,里面有一亿条数据,怎么办?而且更新操作肯定是要写到磁盘上去的,那这中间的 IO 成本呢?如果我有好几十条 update 语句先后更新呢?这样想的话,你就能想到,就这些操作,成本就高的不行,那能不能降低一下这些成本呢?
这就是 redo log 的作用
就是当有一条记录更新的时候, InnoDB 引擎就会先把记录写到 redo log 里面去,同时更新内存,这样就算是更新这条数据成功了
但是此时,它并没有更新到磁盘上去对吧?别担心, InnoDB 会在恰当的时候,把这条及记录更新到磁盘上去
这样的思想或者技术,有个专有名词: WAL 技术,也就是 WriteAheadLogging ,核心就是先写日志,再写磁盘
同样,这里面有个问题
redo log 不能一直写吧?如果更新操作一直写入到 redo log 中的话,不限制大小的话,可能服务器上的存储空间都被 redo log 给占满了
所以 InnoDB 的 redo log 是固定大小的,比如我们配置了一组 4 个文件,每个文件大小是 1GB ,那么它的操作可能就会这样:
能够看到,主要就是 write pos 和 checkpoint , write pos 比较好理解,它就是当前记录的位置,有需要记录的操作就从当前位置向后移,等把 ib_logfile_3 写完之后,就回到 ib_logfile_0 文件开头继续写
checkpoint 是当前要擦除的位置,就是 InnoDB 引擎不是会在恰当的时候,将这些操作进行持久化,更新到磁盘上去,那持久化之后的数据是不是就可以擦除了
write pos 和 checkpoint 之间的部分就是可以用来记录操作的部分,那么如果 write pos 和 checkpoint 相遇了怎么办?相遇了是不是说明这个时候分配的 redo log 大小用完了,那这时候就不能再进行更新操作了,必须停下来处理一下,将 checkpoint 往前推推才行
就是因为有了 redo log ,所以 InnoDB 才可以保证即使数据库发生了异常重启,也没关系,之前提交的记录都还在,只需要根据 redo log 里面的记录进行相应恢复就可以了
所以如果你和 DBA 比较熟的话,可以问问,咱们的 MySQL 是不是可以恢复到半个月内任意一秒的状态,如果对方回答是,别怀疑,他真的不是在吹牛逼
binlog
binlog 是 MySQL Server 层的记录日志,这块举个例子来说吧
在说之前,我们要明白 redo log 和 binlog 的区别:
- redo log 是 InnoDB 引擎特有的, binlog 是 MySQL 的 Server 层实现的,所有的引擎都是可以的
- redo log 是物理日志,记录的是"在 XXX 页上做了 XXX 修改"; binlog 是逻辑日志,比如" 给 id = 2 这一行的 c 字段加 1"
- redo log 是有固定大小的,所以它的空间会用完,如果用完的话,一定要进行一些写入磁盘的操作才可以继续; binlog 是可以追加写入的,也就是 binlog 没有空间的概念,一直写就行了
理解了它们之间区别之后,我们拿一个更新操作来举例
我现在要给 id = 2 这一行的 c 字段加 1,到 MySQL 层面,它是如何去做的呢?
首先,会先找到这条 id = 2 的数据,然后找到 c 字段进行加 1 操作,这个时候,引擎会将这行数据更新到内存中,同时把这个更新操作记录到 redo log 里面,这个时候 redo log 处于 prepare 状态,随后执行器生成这个操作的 binlog ,并且把 binlog 写入到磁盘完成之后,执行器调用引擎的提交事务接口,引擎把刚刚写入的 redo log 从 prepare 状态改成 commit 状态,这样更新操作才算完成
两阶段提交
在上面的描述中,你能发现 redo log 竟然是先 prepare 状态,等 binlog 写完之后,才是 commit 状态,这种方式就叫"两阶段提交"
为什么会有这种方式呢?
redo log 和 binlog 都可以用于表示事务的提交状态,而两阶段提交就是让这两个状态保持逻辑上的一致
可以假设一下,如果不采用这种方式,而是就先写 redo log ,再写 binlog ,会怎样?如果在写 binlog 时,发生了异常,更新操作已经到 redo log 中了,但是此时 binlog 并没有进行更新,是不是出现了数据不一致?
先写 binlog 再写 redo log 也是一样的道理
所以,在写时,先让 redo log 处于 prepare 状态,等 binlog 写完之后,再让 redo log 处于 commit 状态,这样就保持了逻辑上的一致
以上,非常感谢您的阅读哇~