mysql指引(十四):mysql和innodb的结合点:存储引擎接口、binlog与redo

借用前文mysql指引(二):mysql逻辑结构和整体处理流程的图,我们再来回顾下mysql的基本结构:

mysql指引(十四):mysql和innodb的结合点:存储引擎接口、binlog与redo
主要有两块构成:
  1. mysql server 层
  2. mysql 存储引擎层

由于底层引擎层可以进行更换,故和软件开发一样,server层需要制定好接口定义,便于不同的接口实现(存储引擎)接入。如下图所示:

mysql指引(十四):mysql和innodb的结合点:存储引擎接口、binlog与redo
这样就和我们平时开发软件一样,基于接口编程。对 server 层来说,屏蔽底层不同存储引擎实现的细节。对于存储引擎来说,根据接口定义,只要实现了接口,就意味着可以被融入到 mysql 的体系中。最终实现了插件式的存储引擎。

来看下官方是怎么说的:

The storage engines manage data storage and index management for MySQL. The MySQL server communicates with the storage engines through a defined API.

Each storage engine is a class with each instance of the class communicating with the MySQL server through a special handler interface.

这里的API即接口定义,我们来看几个。

查找类方法:

index_first:Retrieve first row in index and return.

index_next:Return next row in index.

index_read:Find a row based on a key and return.

records_in_range:For the given range how many records are estimated to be in this range.Used by optimizer to calculate cost of using a particular index.

增删改方法:update_row、delete_row、write_row

其中,优化器就是调用 records_in_range 方法来计算扫描行数的。

知道了上述这些方法后,感兴趣可以更进一步,来定制属于自己的存储引擎。


下面要说的是关于 binlogredo log 这两种日志。在 mysql server 层,引入了binlog来做归档日志,主要可用于备份恢复,同时也被应用到主从同步等场景。在 mysql 引擎层,innodb引擎引入了 redo log,主要用来做崩溃恢复。

这块内容关键要理解下面几点:

  1. binlog的备份恢复是如何实现的
  2. redo log的崩溃恢复是如何实现的
  3. 为什么需要两阶段提交
  4. 刷盘

其他关于 binlog 的部分,会在分布式 mysql 中继续说明。


binlog日志相关的网上有很多解释,这里只列举几个关键点。Mysql最初的引擎是myisam,Mysql 只在 server 层提供 binlog 记录,存储数据库执行的操作日志,起到归档作用。

binlog日志有三种格式:

  1. statement格式:可理解为直接记录执行的sql语句
  2. row格式:可理解为记录对应的行数据
  3. Mix格式:就是前两者的组合,会自动判断采用哪种

当事务提交时,会将日志内容按照一定格式追加写入磁盘。不会覆盖,所以可以作为备份用。恢复时按照对应格式解析更新即可。


但是这里面有一个问题,假如两个事务A和B,期间都对记录1执行了更新类的操作,假如A先更新记录1,然后B再更新记录1。A晚于B提交事务,那么最终binlog中就会先记录B的更新动作,然后才是A的更新动作。

这样,在从机上同步或者恢复时,就会出现问题。

但,真的会成这样子吗?实际上,前文学习了当前读、锁等概念后,你应该知道这里的描述是错误的。事务B会在更新记录1时被事务A阻塞,知道事务A提交为止。


另外一个问题是,我们说的写到磁盘,是真的落盘了吗?实际上不一定。日志写到binlog中,无非是将日志数据从Mysql的缓存(实际上为 binlog cache)搬移到了操作系统的缓存中(实际上为 page cache)。至于page cache中的数据何时落盘,就依赖操作系统的管理。

当然执行底层调用 fsync() 时就会将缓存数据写入磁盘,才会产生磁盘IO。

可以回顾下讲解 innodb 基本结构的章节,里面官方图中,就有一个标注:O_DIRECT ,即直接写入磁盘,不走操作系统缓存。


最后一个问题是,binlog的结构。binlog文件的组成单位是 binlog event ,每个binlog event 中包含了具体的日志内容,这些内容格式就分为了row、statement这种。实际上不需要了解 binlog event 的具体构成细节,类比数据页结构即可。

来看几个典型的binlog event:

  • query event:在statement格式下,里面包含了对应的sql语句;在row格式下,对应的是begin语句;
  • gtid event:记录GTID版本号(分布式mysql会说到)
  • xid event:当事务提交时,会记录该事件。

那么,redo log的作用呢?这点前文已经讲过了,用来做宕机恢复。


关于binlog 和 redo log这块,总会出现两阶段提交的讨论。指的是redo log 先写成 prepare 状态,然后binlog写入,最后redo log 变成commit状态。

重点是为什么一定需要两阶段提交?不执行两阶段提交的话,主要矛盾点在于:

  • redo log 做主机崩溃恢复
  • binlog 做从机同步

如果 redo log 和 binlog 不同步,那么主机宕机恢复后,就和从机的同步数据不一致,这点是不能允许的。

redo log 和 binlog 都是记录事务的,所以二者之间是根据事务ID关联起来的。 binlog 中 类型为 XID 的event 中就包含了事务ID,和redo log 中的事务ID一致。

所以,假如 redo log 最后是 prepare 状态,那么崩溃恢复时,会根据 redo 中某个事务的事务ID,去 binlog 中查找对应的事务,判断该事务是否具有XID event,不具有则说明该事务未能正确提交,故无需恢复本事务。


最后,我们来简单看下崩溃恢复的整体流程:

  1. 回滚未能 prepare 的事务
  2. 记录已 prepare 但是未能 commit 的事务
  3. 前两步就把 redo log 中的事务扫描并准备好了
  4. 因为 binlog 是追加写入,而崩溃是发生在一瞬间的,故mysql会读取最后一个binglog文件,并判断是否需要执行崩溃恢复流程
  5. 如果需要恢复,则会依次扫描 binlog event,记录已经提交的事务ID(即有 XID event)。
  6. 扫描结束后对比 redo log中的记录的已 prepare,未commit的事务,将匹配到的事务执行 commit 操作,将未能匹配的执行回滚操作。

本篇主要目的是将 mysql 和 innodb 结合起来,针对 binlog 和存储引擎接口进行说明。

接下来,就要开启全新的篇章,即分布式 mysql。从我们已经熟悉的 mysql 上手,理解分布式领域的一些关键概念。