mysql指引(十四):mysql和innodb的结合点:存储引擎接口、binlog与redo
借用前文mysql指引(二):mysql逻辑结构和整体处理流程的图,我们再来回顾下mysql的基本结构:
主要有两块构成:- mysql server 层
- mysql 存储引擎层
由于底层引擎层可以进行更换,故和软件开发一样,server层需要制定好接口定义,便于不同的接口实现(存储引擎)接入。如下图所示:
这样就和我们平时开发软件一样,基于接口编程。对 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 方法来计算扫描行数的。
知道了上述这些方法后,感兴趣可以更进一步,来定制属于自己的存储引擎。
下面要说的是关于 binlog
和 redo log
这两种日志。在 mysql server 层,引入了binlog来做归档日志,主要可用于备份恢复,同时也被应用到主从同步等场景。在 mysql 引擎层,innodb引擎引入了 redo log,主要用来做崩溃恢复。
这块内容关键要理解下面几点:
- binlog的备份恢复是如何实现的
- redo log的崩溃恢复是如何实现的
- 为什么需要两阶段提交
- 刷盘
其他关于 binlog 的部分,会在分布式 mysql 中继续说明。
binlog日志相关的网上有很多解释,这里只列举几个关键点。Mysql最初的引擎是myisam,Mysql 只在 server 层提供 binlog 记录,存储数据库执行的操作日志,起到归档作用。
binlog日志有三种格式:
- statement格式:可理解为直接记录执行的sql语句
- row格式:可理解为记录对应的行数据
- 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,不具有则说明该事务未能正确提交,故无需恢复本事务。
最后,我们来简单看下崩溃恢复的整体流程:
- 回滚未能 prepare 的事务
- 记录已 prepare 但是未能 commit 的事务
- 前两步就把 redo log 中的事务扫描并准备好了
- 因为 binlog 是追加写入,而崩溃是发生在一瞬间的,故mysql会读取最后一个binglog文件,并判断是否需要执行崩溃恢复流程
- 如果需要恢复,则会依次扫描 binlog event,记录已经提交的事务ID(即有 XID event)。
- 扫描结束后对比 redo log中的记录的已 prepare,未commit的事务,将匹配到的事务执行 commit 操作,将未能匹配的执行回滚操作。
本篇主要目的是将 mysql 和 innodb 结合起来,针对 binlog 和存储引擎接口进行说明。
接下来,就要开启全新的篇章,即分布式 mysql。从我们已经熟悉的 mysql 上手,理解分布式领域的一些关键概念。