小巧。快速。可靠。
三者选其二。

安全断电写入

“安全断电写入”(Powersafe overwrite)是 SQLite 团队用来描述某些文件系统和磁盘控制器在断电期间与数据保存相关的行为的一个术语。安全断电写入是一个布尔属性:存储系统要么具有该属性,要么不具有。

如果以下陈述为真,我们则认为系统具有安全断电写入属性。

当应用程序写入文件中的一个字节范围时,即使写入发生在崩溃或断电之前,该范围之外的任何字节都不会发生变化。

安全断电写入属性没有说明写入的字节的状态。这些字节可能包含其旧值、新值、随机值或这些值的某种组合。安全断电写入属性仅声明写入不会更改写入的字节范围之外的字节。

换句话说,安全断电写入意味着当断电发生时,不会出现“附带损害”。只有正在写入的那些字节可能会损坏。

从实际角度来看,安全断电写入属性意味着当磁盘控制器检测到即将发生的断电时,它会在停放磁头之前完成正在写入的扇区。这意味着即使发生断电,单个扇区的写入一旦开始就会完成。

考虑一下如果磁盘扇区写入被断电中断会发生什么。如果应用程序在某个文件中间写入两个或三个字节,操作系统将通过首先读取包含这些字节的整个扇区来实现此操作,在内存中对扇区进行更改,然后将整个扇区写回磁盘。如果在写回过程中发生断电并且扇区未完全写入,则在重新启动后的下一次读取时,扇区中的纠错码可能会检测到无法修复的损坏,并且磁盘控制器将读取该扇区为全零或全一。因此,写入范围之外的值将发生变化,即在应用程序级别写入的两个或三个字节,这违反了安全断电写入属性。

SQLite 对安全断电写入的假设

从 SQLite 3.7.9 版本(2011年11月1日)及之前的所有版本都假设文件系统提供安全断电写入。SQLite 传统上假设当文件的任何一个字节发生变化时,在断电时,该字节同一扇区内的所有其他字节都可能被损坏。在写入时,SQLite 确保将同一扇区内所有修改的字节都写入日志,并且将日志文件填充到下一个扇区边界,以便后续附加到该日志不会损坏之前的记录。SQLite 将扇区大小理解为 VFS 中 xSectorSize 方法返回的值。SQLite 团队经常将 xSectorSize 返回的值称为写入的“爆炸半径”,因为它表示在写入期间发生断电时可能损坏的字节范围。对于 SQLite 3.7.9 版本及之前的所有版本,unix 和 windows 的默认 VFS 始终返回 512 作为所有扇区的大小(或爆炸半径)。

然而,较新的磁盘驱动器已开始使用 4096 字节扇区。从 SQLite 3.7.10 版本(2012年1月16日)开始,SQLite 开发团队尝试更改 xSectorSize 以将 4096 字节报告为爆炸半径。这导致许多数据库的写入开销增加。对于 PRAGMA page_size 为 1024(非常常见的选择)的数据库,对数据库中单个页面的更改现在需要 SQLite 将其他三个相邻页面备份到回滚日志,而以前只需要备份正在更改的页面。在 WAL 模式 下,每个事务都必须填充到 WAL 文件中的下一个 4096 字节边界,而不是下一个 512 字节边界,导致每个事务写入数千个额外的字节。

额外的写入开销促使我们重新审视对安全断电写入的假设。对于现代磁盘驱动器,容量变得非常大,数据密度也变得非常高,因此单个扇区非常小,写入单个扇区所需的时间非常少。我们知道磁盘驱动器可以检测到即将发生的断电并在剩余能量下继续运行一段时间,因为这些驱动器能够在停止旋转之前停放磁头。因此,如果磁盘控制器可以检测到即将发生的断电,那么似乎可以合理地认为控制器将在首次检测到即将发生的断电时完成正在写入的扇区,然后再停放磁头,只要这样做不会花费太长时间,对于小而密集的扇区来说,它不应该花费太长时间。因此,似乎可以合理地假设现代磁盘具有安全断电写入功能。事实上,我们被告知,BerkeleyDB 几十年来一直假设这一点。不过,建议谨慎行事。正如 Roger Binns 在 SQLite 开发人员邮件列表中指出的那样:“‘编写不佳’应该是对驱动程序固件的主要假设。”

页面撕裂

当数据库页面大于磁盘扇区、数据库页面被写入磁盘,但断电发生在数据库页面的所有扇区都被写入之前时,就会发生页面撕裂。然后,在恢复后,数据库页面的部分将具有旧内容,而页面的其他部分将具有新内容。某些数据库引擎假设页面写入是原子的,因此页面撕裂是不可恢复的错误。

SQLite 从不假设数据库页面写入是原子的,无论 PSOW 设置如何。(1) 因此,SQLite 始终能够自动从崩溃引起的页面撕裂中恢复。启用 PSOW 不会降低 SQLite 从页面撕裂中恢复的能力。

SQLite 3.7.10 版本中的更改

SQLite 3.7.10 版本(2012年1月16日)的 VFS 添加了一个名为 SQLITE_IOCAP_POWERSAFE_OVERWRITE 的新设备特性。报告此特性的数据库文件被假定驻留在具有安全断电写入属性的存储系统上。默认的 unix 和 windows VFSes 现在报告 SQLITE_IOCAP_POWERSAFE_OVERWRITE,如果 SQLite 使用 -DSQLITE_POWERSAFE_OVERWRITE=1 编译,或者如果使用 -DSQLITE_POWERSAFE_OVERWRITE=0 编译,则它们会做出存储不具有安全断电写入属性的传统假设。目前,默认情况下会启用安全断电写入,但我们将来可能会重新考虑这一点并将其默认禁用。

可以使用“psow”查询参数和 URI 文件名 在打开数据库时指定单个数据库的安全断电写入属性。例如,要始终为文件假设安全断电写入(可能为了确保最大写入性能),请将其打开为

file:somefile.db?psow=1

或者为了额外确保数据库的安全并强制 SQLite 假设数据库缺少安全断电写入,请使用以下方式打开它:

file:somefile.db?psow=0

还有一个新的 SQLITE_FCNTL_POWERSAFE_OVERWRITE 操作码用于 sqlite3_file_control(),允许应用程序查询数据库文件是否存在安全断电写入属性。


注释

  1. SQLite 在其默认配置中从不假设页面写入是原子的。但是,自定义 VFS 可以设置 xDeviceCharacteristic() 方法结果中的 SQLITE_IOCAP_ATOMIC 位之一,然后 SQLite 将假设页面写入是原子的。但是,应用程序必须提供自定义 VFS 才能实现此目的,因为没有一个标准 VFS 会在 xDeviceCharacteristics() 向量中设置任何原子位。

此页面上次修改于 2022-01-08 05:02:57 UTC