Skip to main content

05 Automicity

原子性

在一个原子单元中的操作,要么都发生,要么都不发生。

第一种设计是创建副本。创建副本,对副本写入,如果写入都成功,则替换原有数据。在重命名阶段可能导致问题(两个名称指向同一个文件,但ref是1);这种设计需要将应用程序与文件系统耦合,需要应用程序进行对应的修改。

Journal

  • 在日志中记录修改
  • 提交修改
  • 更新

如果提交之前报错,数据还未被修改,丢弃日志即可;如果提交后报错,日志里已经有了所需要的数据,可以直接恢复。

所有数据都会写入磁盘两次,如果文件较大会影响性能。可以只保护元数据,或只保护部分数据。

以上的设计假设,向磁盘某个sector的写入是全有或全无的。如果日志数据大于某个sector,就需要更通用的方法。

影子副本

修改时,全部在副本中进行;对于重命名操作(将副本切换到正式文件),交给日志,原子性进行。系统会扫描并删除临时性的文件。

缺点:

  • 多个用户不能同时操作,并行可能变成串行
  • 影子副本原子性无法扩展到多个文件
  • 无法推广到多个文件/目录
  • 任何修改都需要复制整个文件

Logging

logging相对而言更面向用户,需要用户确认哪些操作是原子性的(即位于一个事务中)。如果超过了某行代码,则事务一定成功;这一行就称为commit checkpoint。

redo

写入磁盘之前,一定要同步日志到磁盘;通过checksum保证日志条目完整。日志成功后,可以更新磁盘。如果系统崩溃,扫描日志,重做所有更新。

优点:

  • 提交高效,只有一次append操作

缺点:

  • 所有更新缓存在内存
  • 日志文件持续增大

朴素方式是换页,但难以性能分析等。

undo

允许把未提交的事务写入磁盘;如果崩溃,用日志撤销未提交事务。

只有undo策略,不保证事务提交后一定写入了磁盘。可以用sink等方式确定写入,但会导致较大的开销。

undo-redo

和redo不同(追加完整事务条目),undo-redo追加的是操作记录,可能来自不同的事务,需要指针追踪。

这种设计下,每条日志包括

  • 事务ID
  • 操作ID
  • 指向前一记录的指针
  • 具体值

恢复:

  • 从尾到头,给所有无CMT事务打abort标记
  • 从尾到头,执行abort日志的undo
  • 从头到尾,执行cmt日志的redo

总结

redo日志通常比undo-redo更优,磁盘操作更少,仅需一次扫描。仅undo日志比undo-redo慢得多,实际少用。

检查点

以上的日志都没有解决日志文件不断变大的遗留问题。可以将检查点理解为对部分日志打包,一旦打包完成,原有的日志就可以丢弃。

  • 等待没有操作进行
  • 写检查点到日志
  • 刷页缓存
  • 除检查点外丢弃所有日志