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慢得多,实际少用。
检查点
以上的日志都没有解决日志文件不断变大的遗留问题。可以将检查点理解为对部分日志打包,一旦打包完成,原有的日志就可以丢弃。
- 等待没有操作进行
- 写检查点到日志
- 刷页缓存
- 除检查点外丢弃所有日志