资讯动态

Turso 事务正确性指南:WAL 机制、并发规则与崩溃恢复的源码级解析

发布时间:2026/9/12 14:59:49 来源:尧图企业网站定制
Turso 事务正确性指南WAL 机制、并发规则与崩溃恢复的源码级解析【免费下载链接】tursoA SQL database in Rust: SQLite-compatible, now also speaking Postgres (experimental). The LLVM of databases.项目地址: https://gitcode.com/GitHub_Trending/tu/tursoTursolimbo是一个用 Rust 重写的 SQLite 兼容数据库其存储层完全围绕 WALWrite-Ahead Logging预写日志模式设计。本文以仓库中 docs/agent-guides/transaction-correctness.md 为骨架结合 core/storage/wal.rs、core/storage/pager.rs 等核心源码系统讲解 Turso 的 WAL 读写路径、检查点Checkpoint四种模式、并发约束与崩溃恢复流程并深入其连接私有与跨连接共享两类状态的划分及四条正确性不变量。读完本文你将能理解 Turso 如何在无-shm共享内存文件的前提下实现 SQLite 级别的快照隔离与崩溃安全。总览Turso 只使用 WAL 模式Turso 与 SQLite 的一个重要差异是Turso 只支持 WAL 模式不支持 rollback journal 模式。文档明确声明 Turso uses WAL (Write-Ahead Logging) mode exclusively并给出了典型的数据文件构成.db主数据库文件保存已 checkpoint 的页面.db-walWAL 日志文件顺序追加尚未回填到主库的页面帧frame没有.db-shmSQLite 依赖共享内存文件承载 WAL 索引WAL-index而 Turso 因为不支持多进程访问multi-process access改用进程内的内存数据结构frame_cache哈希表与原子读标记来替代见 core/storage/wal.rs 中WalSharedRuntime::frame_cache的注释One difference between SQLite and limbo is that we will never support multi process, meaning we dont need WALs index file. So we can do stuff like this without shared memory.这一设计决定了 Turso 当前的并发模型是**进程内多连接multi-connection**而非跨进程多实例是理解后续所有机制的前提。WAL 机制详解写入路径Write PathWAL 的核心思想是先写日志、后落主库Turso 的写入路径分三步顺序追加帧写事务把页面数据frame按序追加到 WAL 文件末尾对应源码中的append_frames_vectoredcore/storage/wal.rs采用顺序 I/O性能友好COMMIT 即提交帧事务的最后一帧会在帧头写入非零的db_size数据库页数标记事务结束。源码中PreparedFrames.db_size_on_commit的注释说明db_size被设置代表事务完成后数据库的页数且是该事务写入的最后一帧db_size为None代表中间帧core/storage/wal.rs 约 718-735 行。这个帧头字段正是检查点与恢复判断事务边界的依据主库保持原样直到检查点执行前.db主文件内容不发生任何改变所有新数据只存在于 WAL 中。读取路径Read Path读取路径保证了每个读者看到一个一致的快照获取读标记read mark读者在begin_read_tx时取得一个 WAL 位置快照即mxFrame——最后一个有效提交帧的编号。源码中 core/storage/pager.rs 的wal_pos()把连接冻结的 WAL 位置描述为(checkpoint_seq, max_frame)即该连接的读标记begin_read_tx内部调用WalFile::begin_read_tx并在此过程中发现数据库有变化时清空本地页缓存、失效 schema cookiecore/storage/pager.rs 3194-3206 行按页查找读取某个页面时先在 WAL 中[min_frame, mxFrame]范围内查找该页的最新帧找不到再回落到主.db文件。源码中WalFile::find_frame与WalSnapshot::min_framenbackfills 1即检查点回填后 WAL 中仍然可见的第一帧见 core/storage/wal.rs 202-205 行共同定义了查找区间一致快照由于读者只观察到自己读标记之前已提交的帧后续写入不会影响它的视野天然形成快照隔离。检查点Checkpointing检查点把 WAL 内容回填backfill到主数据库流程如文档所示WAL grows → checkpoint triggered (default: 1000 pages) → pages copied to DB → WAL reused其中默认 1000 页触发对应源码中checkpoint_threshold: 1000的初始化core/storage/wal.rs 4737 行触发判定在should_checkpoint()当max_frame checkpoint_threshold nbackfills时发起检查点core/storage/wal.rs 3991-3994 行。Turso 完整继承了 SQLite 的四种检查点模式定义在CheckpointMode枚举core/storage/wal.rs 158-175 行模式行为是否阻塞读者/写者源码要点PASSIVE非阻塞尽可能多地回填遇到活动读者需要的页就停下若日志全部回填则同步主库不阻塞读者也不阻塞写者只保证同一时刻只有一个检查点在跑支持upper_bound_inclusive上限参数FULL等到没有写者、且所有读者都基于最新数据库快照后回填全部帧并同步主库阻塞新写者但不阻塞新读者require_all_backfilled()为真RESTART与 FULL 相同额外等所有读者只读主库后把 WAL 从开头重新开始同上且阻塞到读者全部脱离 WALshould_restart_log()为真TRUNCATE与 RESTART 相同额外在返回前把 WAL 文件截断为 0 字节同上支持upper_bound_inclusive用于条件截断被 sync-engine 用来在检查点前合并 WAL、确保不遗漏帧从源码实现看PASSIVE 之外的模式都需要完整回填nbackfills 1到max_frame之间的所有帧require_all_backfilledcore/storage/wal.rs 186-188 行并持有checkpoint_lock串行化检查点线程同一时刻仅一个检查点在途。检查点完成后会发布nbackfills进度publish_backfill并通过install_durable_backfill_proof安装持久化回填证明确保崩溃后恢复不会重放已经回填的帧。WAL 索引用内存结构替代-shmSQLite 通过-shm共享内存文件记录页到帧的映射WAL-index。Turso 用WalSharedRuntimecore/storage/wal.rs 2822-2865 行中的进程内结构取代它frame_cache: FxHashMapu64, Vecu64页号到帧号列表的索引配合frame_cache_high_water水位线检测帧槽位复用/回卷防止find_frame返回已被覆盖的陈旧映射read_locks: [TursoRwLock; 5]5 个读标记槽位每个TursoRwLock是64 位原子读写锁 内嵌 32 位值存储高 32 位存值、bit0 为写者位、其余为读者计数见 core/storage/wal.rs 261-274 行注释槽位 0 特殊当它被共享持有时读者完全绕过 WAL 直接读主库槽位 1 是默认读锁存放 WAL 中的max_framewrite_lock独占写者锁保证同一时刻只有一个写者checkpoint_lock检查点串行化锁vacuum_lock原地 VACUUM 期间的排他锁普通 WAL 事务在事务生命周期内共享持有它VACUUM 排他持有直到最终截断式检查点完成epoch每次检查点递增用于防止陈旧缓存页被用于回填。这些结构共同实现了与 SQLite WAL-index 等价但纯内存化的并发协调层。并发规则文档给出三条并发铁律源码均能找到对应实现同一时刻只有一个写者由WalSharedRuntime::write_lock独占保证对应 SQLite 的 WAL_WRITE_LOCK 语义读者不阻塞写者写者不阻塞读者写入只追加 WAL 帧读者只在自己的读标记范围内查找帧两者路径互不交叉检查点必须在活动读者需要的页面处停下PASSIVE 模式天然遵守FULL/RESTART/TRUNCATE 模式则通过等待读者离开 WAL 后再完整回填来满足。读标记槽位的语义检查点只能回填不超过所有在用读标记中最小值的帧与 SQLiteaReadMark[]的设计一致core/storage/wal.rs 2752-2805 行直接引用了 sqlite3/src/wal.c 的注释说明这一经典协议。此外begin_read_tx在发现其他人改过数据库时会清空本连接的页缓存并失效 schema cookiecore/storage/pager.rs 3194-3206 行避免跨连接读到陈旧页面——这是进程内多连接下保持快照正确性的关键一环。崩溃恢复RecoveryTurso 的恢复协议沿用 SQLite 的流程文档概括为三步首个连接获取排他锁打开数据库时通过open_shared_if_exists/open_shared_if_exists_begincore/storage/wal.rs 5778-5790 行建立进程内共享的 WAL 状态并启动恢复扫描从 WAL 重放有效提交恢复由一个非阻塞状态机驱动——BuildSharedWalcore/storage/sqlite3_ondisk.rs 1487-1506 行以NeedHeaderRead → AwaitHeader → ChunkLoop → Done的相位推进流式扫描 WAL 帧只有带非零db_size的提交帧才构成有效事务边界部分写入无提交帧的事务会被丢弃从而保证原子性释放锁恢复正常操作恢复完成后共享状态标记为loaded其余连接可正常接入。Turso 对恢复安全还有更精细的保证nbackfills计数只有在帧持久化回填之后才前进因此恢复永远不会重放已经检查点过的帧core/storage/pager.rs 4927 行附近有 aristo 意图注解wal_nbackfills_orders_with_recovery佐证检查点中途崩溃造成的部分回填状态也可被 WAL 恢复治愈core/storage/wal.rs 746 行注释a crash mid-backfill can always be healed by WAL recovery。整个 WAL 子系统在源码中以wal_protocol_correctness意图标注了 LSN 单调性、帧提交有序性、恢复幂等性、检查点安全性与组提交原子性等协议不变量core/storage/wal.rs 621 行。Turso 实现连接私有与跨连接共享的状态划分文档把 WAL 相关状态清晰地划分为两层源码中一一对应连接私有Per-ConnectionPagercore/storage/pager.rs页缓存、脏页集合、savepoint、提交状态属于单个连接WalFilecore/storage/wal.rs 2695-2750 行连接对 WAL 的快照视图核心字段包括max_frame/min_frame本连接快照可查找帧的区间(min_frame, max_frame]max_frame_read_lock_index本连接持有的读锁槽位下标last_checksum滚动校验和状态WAL 帧校验和的高低位合并在一个原子字中无锁读写checkpoint_seq、transaction_count、dirty标志WAL 自上次成功 fsync 后是否又有新帧用于synchronousFULL下提交前的 fsync 判定checkpoint_guard与vacuum_lock_guard检查点与 VACUUM 的锁守卫。跨连接共享Shared across connectionsWalFileSharedcore/storage/wal.rs 2885-2888 行全局 WAL 状态由metadata: WalSharedMetadata权威元数据enabled、wal_header、min_frame/max_frame/nbackfills、transaction_count、last_checksum、loaded/initialized等core/storage/wal.rs 2807-2819 行与runtime: WalSharedRuntime前文已述的frame_cache、5 个read_locks、write_lock、checkpoint_lock、vacuum_lock、epoch、file句柄等组成DatabaseStorage主.db文件core/storage/database.rsBufferPoolcore/storage/buffer_pool.rs进程级共享内存分配器。读者在begin_read_tx时从共享状态抓取一份不可变的WalSnapshotmax_frame、nbackfills、last_checksum、checkpoint_seq、transaction_countcore/storage/wal.rs 191-199 行配合持有的读标记ReadGuardKind无标记 / 直读主库 / 读标记槽位core/storage/wal.rs 209-234 行构成连接本地状态WalConnectionState——这是每连接快照视图 全局共享协调的典型实现。正确性不变量Correctness Invariants文档归纳的四条不变量是理解 Turso 存储层设计目标的总纲持久性DurabilityCOMMIT 记录在返回成功前必须完成 fsync。源码中提交状态机在写完成之后收敛到等待 WAL fsync 的状态core/storage/pager.rs 1173-1186 行附近注释Wait for the WAL fsync that makes the commit durable且 WAL 的dirty标志精确记录自上次成功 fsync 后追加了新帧从而在synchronousFULL下保证提交不被丢失原子性Atomicity部分提交的事务对读者永不可见。因为帧只有在带有非零db_size的提交帧出现后才成为max_frame的可观察边界未完成事务的中间帧不会被读者当作有效快照内容隔离性Isolation每个读者在其读标记处看到一致的快照写者追加的新帧不会进入既有读者的视野无丢失更新No lost updates检查点不能覆盖未提交的更改——回填只针对已提交帧区间(nbackfills, max_frame]且读标记协议保证检查点停在活动读者所需帧之前publish_backfill前的turso_assert!也会校验nbackfills ≤ max_frame ≤ 总帧数的范围约束core/storage/wal.rs 4046-4049 行。延伸阅读WAL 核心实现core/storage/wal.rs含CheckpointMode、WalFile、WalFileShared、WalSharedRuntime等全部关键结构页面管理与事务core/storage/pager.rsbegin_read_tx、begin_write_tx、commit_tx、提交状态机与 fsync 流程WAL 磁盘格式与恢复状态机core/storage/sqlite3_ondisk.rsBuildSharedWal恢复驱动、帧头/校验和编解码主库文件与缓冲池core/storage/database.rs、core/storage/buffer_pool.rs存储层整体设计官方 SQLite WAL 文档https://sqlite.org/wal.html与 WAL 文件格式https://sqlite.org/walformat.html可作为理解 Turso 协议继承关系的外部参考Turso 的帧格式与校验和算法与 SQLite 保持兼容。【免费下载链接】tursoA SQL database in Rust: SQLite-compatible, now also speaking Postgres (experimental). The LLVM of databases.项目地址: https://gitcode.com/GitHub_Trending/tu/turso创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价