Libertus Chen-U
  1. 1 Libertus Chen-U
  2. 2 Last Surprise Lyn
  3. 3 Life Will Change Lyn
  4. 4 かかってこいよ NakamuraEmi
  5. 5 Hypocrite Nush
  6. 6 Warcry mpi
  7. 7 The Night We Stood Lyn
  8. 8 Quiet Storm Lyn
  9. 9 Time Bomb Veela
  10. 10 One Last You Jen Bird
  11. 11 Flower Of Life 发热巫女
2019-10-03 16:32:47

聊聊MySQL的日志设计

作为前端,虽然和数据库打交道不多,但还是很有必要学一学的。一是如果接触Node,就免不了接触到后端和数据库知识,打通整套开发体系也能给予我们更广阔的视野。二是数据库的一些设计确实很精妙,这些思想在平时开发中也可以有意识的学以致用。当然语法就不细谈了,文档都能查到,主要是打算写一系列解析数据库设计的文章。第一篇就从非常重要的日志开始吧。

日志的定位

老生常谈的,在学习一项技术之前,我们必须要搞清楚它能解决什么样的问题,任何能流行起来的技术绝不是空中楼阁,只有找到技术针对的痛点,我们才能理解其中一些设计的精妙之处。

日志这个东西,其实现在的形式已经非常宽泛了,从最简单的console.log到日志文件,日志服务器,图形化日志等等,但究其本质,其实就是一个只能按时间排序并增加的一系列快速记录。想想我们平时用日志主要干什么?排错,分析程序运行信息,都是对数据真实性要求相当高的动作,因为日志的特性决定了其是高度可信赖的数据。当然对于我们前端而言,日志的意义到这一步也就停止了,平时也没有太重视,但在数据库这一层面,高度可信赖的数据是非常有价值的东西,甚至比真实的数据还有价值。因为真实数据的落盘是随机且性能消耗较大的操作,所以根据日志的顺序写和快速记录的特点,可以承担对真实数据做节流提高写性能和保障真实数据可靠性两大职责。

数据库中的一等公民不是数据,而是日志。听起来是不是挺反直觉的,但其实把日志当做真实数据,把平时被我们称作数据的玩意当做一份针对日志的缓存才是常规操作,这样也能更好的理解以后要谈到的许多数据库设计问题。那么接下来就带着对日志的认识,看看MySQL中的日志系统实现吧。

redo log

MySQL的整体架构上可以分为两部分,一部分是Server层,主要处理MySQL的功能,比如管理连接权限,词法语法分析等。另一部分是插件式的存储层,负责数据的存取,支持InnoDB,Memory等多种存储引擎,目前最常用的也是默认的就是InnoDB了。

InnoDB内部的日志系统就是redo log,既然在存储层,其首先要解决的一个问题就是高成本的磁盘IO,方案也就是数据库中经典的WAL(Write-Ahead Logging)了。当数据需要被更新时,先把操作记录到redo log上,同时把数据写入内存数据页,在空闲的时间,InnoDB会把redo log上的记录对应的内存数据页flush到磁盘数据页上。

当然这样的设计会带出一些问题,比如如果一直没有空闲时间,redo log空间满了怎么办?先看看redo log的结构吧。

其可以配置为多个文件,文件首尾相连设计为环形,当一个文件写满后就写入下一个文件,其关键点有两个——write pos和check point。前者代表着当前的写入位置,后者代表着已存储到磁盘的位置,两者内环就是已写到redo log但还未同步到磁盘的数据,两者外环就是剩余空间。当write pos追上check point时,就会强制同步一些内环数据保证外环有空间。

redo log在保证写入性能的同时还带来的一个能力就是crash-safe。只要redo log存在,InnoDB就可以保证即使数据库异常或崩溃,也能根据redo log记录恢复数据库。谈到数据恢复,就引申出下一个问题,我们如何恢复到某一个精确时间节点的数据库状态?这是很常见的需求,比如周五的时候发现周一有一次误删表的操作,那就需要恢复到周一前的状态找回数据,而按照redo log的循环写入设计来看,似乎做不到这一点。这里就要谈到MySQL中的另外一种日志了。

bin log

正如前文提到的,MySQL分为Server层和存储层,bin log负责的就是Server层的日志。和redo log日志的区别大致有如下几点:

  • redo log是物理日志,记录了数据页上的修改。bin log是逻辑日志,记录了原始语句。
  • redo log是InnoDB特有的不通用日志,bin log属于Server层通用日志,供所有引擎使用。
  • redo log 循环写,bin log追加写。
  • redo log主要用来保证异常数据的恢复,binlog主要用来选择时间点恢复,延伸开来可以应用到主从复制。

回到上面的问题,要恢复到任一时间点的数据库状态,只要找到这个时间点前的一次全量备份,然后从这个备份开始,将binlog重放到需要的时间点即可。

两阶段提交

现在知道了每次数据库更新操作会有两种日志的更新,那么不可忽视的一致性问题就出来了。最简单的,假设某一条数据更新时,redo log写好之后,bin log还来不及写时数据库崩溃,恢复后由于redo log的机制记录了这条数据更新,但bin log丢失了这条语句,这样导致以后用bin log做数据恢复的时候就会丢失掉这次更新,所以肯定需要一种机制保证两种日志的一致性。流程如下:

  1. Server层的执行器根据语法解析结果找到需要更新的数据行
  2. 更新行数据,调用存储引擎接口写入行数据
  3. 引擎将这行新数据更新到内存数据页中,同时记录这个更新操作到redo log上,并标记该redo log为prepare状态。
  4. 存储引擎告知执行器已完成,执行器记录bin log,并把bin log写入磁盘。
  5. 执行器通知存储引擎将redo log改写为commit状态,redo log写入成功。

整个流程比较特别的点就在于将redo log的写入拆分为二步,用来保证和binlog的同时记录,这就是两阶段提交了。好,来个俄罗斯套娃问题,既然两阶段提交保证了数据一致性,那如果在两阶段提交过程中数据库崩溃呢?不妨仔细分析一下。

  • 写入redo之后,写入bin之前。

因为binlog没写,就没有完成两阶段提交,redo也不处于commit状态,该事务会回滚,两种日志都没有写入保持一致,很好理解。

  • 写入bin后,redo变更为commit前。

这里比较复杂,主要涉及到redo在崩溃时的恢复判断。如果redo有commit标识后崩溃恢复会判断为已记录,如果redo在prepare前会判断为未记录,而在prepare和commit中间时,就需要借助bin来共同决定了。binlog和redo log有一个叫XID的共同的数据字段,redo log会依据该字段找到对应binlog然后判断binlog是否完整,如果是就提交事务,否则回滚。

这里因为写入bin已经是完整的,所以崩溃恢复后会提交事务,两种日志都写入保持一致。综合上述两种特殊情况可以看到两阶段提交的设计对于维护数据一致性考虑的还是很完备的。

小结

这篇文章对MySQL里的两种日志设计做了个基本介绍,其实还有挺多细节的东西可以说,不过需要结合其他的知识点一起谈,慢慢来吧。

-- EOF --

添加在分类「 前端开发 」下,并被添加 「数据库」 标签。