小记
最近在开发中偶然遇到了几种数据库设计之一:WAL模式。
SQLite 的 WAL(Write-Ahead Logging,预写式日志) 与传统的“回滚”日志的概念截然相反。
它注重于修改数据时,不直接写入主数据库文件,而是先将变更追加到一个单独的 .db-wal 文件中。只有在事务提交后,这些变更才会在某个时间点被合并回主数据库文件。[1]
模式如下:
展开图表
WAL模式并发性的根本是基于“读写操作交互”的改变。
- 写操作:数据库事务进行的时候,所有修改顺序都追加到
.db-wal后缀,与数据库文件同名的一个文件的末尾,一个愿意是避免了随机写入主.db文件,使磁盘I/O更有顺序。 - 读操作:当读事务开始的时候,它选择记住当前“WAL”文件的状态快照,并只修改在这个快照以前已经提交过的数据。后续其他事务的写入,对它来说完全透明。
由此:WAL实现了“读不阻塞写,写不阻塞读”的概念,读写可以同时进行。
检查点
WAL基于检查点机制并不会无限生长,检查点机制会在每一个检查点,将yourdbName.db-wal文件中已提交更新的数据合并到主库。
- 手动控制:你可以选择主动
PRAGMA wal_checkpoint;命令来手动设置检查点。
注意:文件构成变化 启用 WAL 后,数据库会关联 .db-wal 和 .db-shm(共享内存索引)文件。所以说删除或移动数据库时,必须同时处理这三个文件,单独保留主文件可能导致数据库损坏或状态不一致。
不支持大型事务:WAL 模式在处理非常大的事务时表现不佳,因为整个事务的变更都会堆积在 WAL 文件中。
无法更改页面大小:一旦进入 WAL 模式,就无法再修改数据库的 page_size,必须先切换回其他日志模式才能更改。
纯读场景额外开销:如果一个应用几乎只有读操作,WAL 模式可能比传统模式慢 1%-2%,因为读操作需要额外检查 WAL 文件。
fsync()
那正好借着这个机会说一下一款系统调用:fsync。
- 函数签名:
int fsync(int fd);
Describe:fsync() 是 Unix/Linux 下的一个系统调用。
作用:把某个文件描述符对应的文件数据和元数据,从操作系统内核的页缓存**(page cache)强制刷写到持久化存储设备并等待刷写完成**后才返回。
它的调用有一种含义:调用成功后,表示该文件此前写入的数据,以及相关必要的元数据已经被安全落盘到存储设备当中。
- 在
fsync()成功返回前,任何操作都不会被试作为一种成功“落盘”。