<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Sqlite on codedump notes</title>
    <link>https://www.codedump.info/zh/tags/sqlite/</link>
    <description>Recent content in Sqlite on codedump notes</description>
    <generator>Hugo</generator>
    <language>zh</language>
    <lastBuildDate>Sun, 04 Sep 2022 09:10:31 +0800</lastBuildDate>
    <atom:link href="https://www.codedump.info/zh/tags/sqlite/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>周刊（第24期）：sqlite并发读写的演进之路</title>
      <link>https://www.codedump.info/zh/post/20220904-weekly-24/</link>
      <pubDate>Sun, 04 Sep 2022 09:10:31 +0800</pubDate>
      <guid>https://www.codedump.info/zh/post/20220904-weekly-24/</guid>
      <description>&lt;hr&gt;&#xA;&lt;p&gt;引言：本文梳理sqlite并发读写方案的演进之路。&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;sqlite并发读写的演进之路&#34;&gt;&#xA;  sqlite并发读写的演进之路&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#sqlite%e5%b9%b6%e5%8f%91%e8%af%bb%e5%86%99%e7%9a%84%e6%bc%94%e8%bf%9b%e4%b9%8b%e8%b7%af&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;概论&#34;&gt;&#xA;  概论&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e6%a6%82%e8%ae%ba&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;sqlite底层的存储基于B-tree，B-Tree对底层存储的基本读写单位是页面，而每个页面都由全局唯一的页面编号与之对应，一般来说页面编号从1开始递增。&lt;/p&gt;&#xA;&lt;p&gt;类B-Tree的存储引擎修改数据的流程如下图所示：&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;figure class=&#34;&#34;&gt;&#xA;&#xA;    &lt;div&gt;&#xA;        &lt;img loading=&#34;lazy&#34; alt=&#34;b-tree&#34; src=&#34;https://www.codedump.info/media/imgs/20220904-weekly-24/b-tree.png&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; b-tree &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;从上图中，需要区分B-Tree类的存储引擎几个核心的模块：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;B-Tree算法模块：从页面管理器中读取页面到内存，进行逻辑的修改，修改完毕之后标记该页面为脏页面，这样页面管理器就知道哪些页面被修改，后续需要进行落盘。&lt;/li&gt;&#xA;&lt;li&gt;页面管理器：负责向B-Tree算法模块提供根据页面编号读、写页面的接口。&lt;/li&gt;&#xA;&lt;li&gt;数据库文件：这其实不是一个模块，泛指在磁盘上的数据库相关文件，任何的修改最终都要落到数据库文件。在sqlite中，数据库文件是单一文件，在其他存储引擎里可能是一组相关的文件。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;最上层的B-Tree算法模块，在进行写事务的时候，是首先向页面管理器发起读页面到内存中的请求，注意到B-Tree模块并不会直接跟数据库文件打交道，而是经过页面管理器模块（下面会展开说），修改了页面之后标记为“脏页面”，页面管理器最终负责将脏页面落盘到数据库文件中。&lt;/p&gt;&#xA;&lt;p&gt;现在来谈谈“页面管理器”模块的具体工作，也有的实现称为“缓存管理器（buffer manager）”。这个模块负责：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;在内存中管理页面，这涉及到两部分内容：&#xA;&lt;ul&gt;&#xA;&lt;li&gt;如果页面当前不在内存中，需要根据页面编号到磁盘上加载页面。&lt;/li&gt;&#xA;&lt;li&gt;页面也并不是每一次读写时都要到磁盘上加载，有些时候页面已经在缓存中存在了，这种情况下不需要到磁盘上加载页面数据。于是，“页面管理器”模块还需要负责维护这些内存中的页面缓存，何时淘汰这些页面、淘汰哪些内存中的页面、何时真正从磁盘上加载，都是这个模块的工作。&lt;/li&gt;&#xA;&lt;li&gt;对外部而言（这里的外部更多的是B-Tree算法模块），其实不需要也看不到页面缓存的细节，页面管理器对外提供根据页面编号读、写页面接口即可。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;错误的恢复、事务的管理。比如：&#xA;&lt;ul&gt;&#xA;&lt;li&gt;一次事务要修改N个页面，修改到中间的时候，进程崩溃了，这时候重新启动时需要恢复到这个事务之前的数据成功启动，即需要提供回滚事务的功能。&lt;/li&gt;&#xA;&lt;li&gt;同样的一个事务要修改N个页面，在事务还未提交的时候，如果事务级别不是read uncommitted， 那么前面的修改效果不能被其他事务可见，这也是页面管理器需要做的事情，毕竟它对外提供了读、写页面的接口，同一个页面编号的页面什么时候的内容可见都由它来决定。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;有了这些基础的了解，我们来看看sqlite在并发读写方面的演进之路。&lt;/p&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;journal&#34;&gt;&#xA;  Journal&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#journal&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;最早的页面管理器实现是基于Journal文件的，这个文件存储页面在修改之前的内容：&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;figure class=&#34;&#34;&gt;&#xA;&#xA;    &lt;div&gt;&#xA;        &lt;img loading=&#34;lazy&#34; alt=&#34;journal&#34; src=&#34;https://www.codedump.info/media/imgs/20220904-weekly-24/journal.png&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; journal &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;可以看到的是：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Journal文件存储了一个事务所要修改的页面在修改之前的内容，这个定义有点拗口，姑且称为“旧页面内容”。&lt;/li&gt;&#xA;&lt;li&gt;每次一个事务提交之后，意味着这个事务所有队页面的修改都已经落到了数据库文件中，这时候Journal文件里保存的旧页面内容就不再需要了，可以被删除了。&lt;/li&gt;&#xA;&lt;li&gt;由于每次事务修改都要落盘到数据库文件，这些落盘操作涉及到多次磁盘寻道，即一次事务多次随机磁盘寻道，这样代价其实是很大的。&lt;/li&gt;&#xA;&lt;li&gt;当需要事务回滚的功能时，页面管理器就可以从Journal文件中读出来旧页面内容覆盖回去。&lt;/li&gt;&#xA;&lt;li&gt;虽然这个算法很简单，但是缺陷也明显：它没有任何的读写并发支持。每次开始一个写事务，从开始写事务，到这个写事务提交完成的过程中间，其他的读写事务都不能开始，可以说是“一写全卡住”。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;wal&#34;&gt;&#xA;  WAL&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#wal&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;从上面的分析可以看出，以Journal文件的机制，每次写事务：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;需要把内容修改全部落盘到数据库文件才能算完成。&lt;/li&gt;&#xA;&lt;li&gt;这个过程中间，不能同时存在其他并发的读、写操作。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;从sqlite3.7.0版本开始（&lt;a href=&#34;https://www.sqlite.org/releaselog/3_7_0.html&#34;&gt;SQLite Release 3.7.0 On 2010-07-21&lt;/a&gt;），sqlite引入了更常见的WAL机制来解决页面的读写并发问题，WAL的原理如下图所示：&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;figure class=&#34;&#34;&gt;&#xA;&#xA;    &lt;div&gt;&#xA;        &lt;img loading=&#34;lazy&#34; alt=&#34;wal&#34; src=&#34;https://www.codedump.info/media/imgs/20220904-weekly-24/wal.png&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; wal &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;WAL机制中，事务对页面的修改：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;并没有马上落到数据库文件里，而是首先写入WAL文件中。这样有两个好处：&#xA;&lt;ul&gt;&#xA;&lt;li&gt;WAL文件是append-only的文件，在文件结尾处添加新内容，对写磁盘文件这种操作而言是更快的，因为少了很多磁盘寻道的流程。&lt;/li&gt;&#xA;&lt;li&gt;由于事务的修改并没有马上落盘到数据库文件，所以就并不可见，后续如果需要回滚事务的修改也更容易：不要这个事务修改的那部分WAL内容即可。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;由于修改有时候还未落盘，需要维护一个wal中页面的索引，用于根据页面编号定位到WAL中的页面。由于wal索引可以控制哪些wal文件内容“可见”，于是就能控制未提交的事务修改对读操作并不可见了。&lt;/li&gt;&#xA;&lt;li&gt;WAL文件不能一直增长下去，需要定期把WAL文件中已经提交的事务修改内容落盘到数据库文件，这个流程被称为“checkpoint”。在“checkpoint”之后，wal索引就可以修改了。虽然checkpoint过程将WAL文件中的内容落盘到数据库文件，仍然是针对数据库文件的随机写流程，有很多磁盘寻道操作，但是由于一次checkpoint累计了多次写事务一次性落盘，代价小了一些。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;有了WAL之后，读写并发有了一些改善：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;虽然同一时间仍然只能有一个写事务在进行，但是读事务同时存在多个。其核心原因是因为修改并没有马上直接落盘到数据库文件中，这样修改的可见性就可以由wal索引来控制，即：写事务尽管写，读事务尽管读，只要控制这些写事务的修改不在wal索引中可见即可。&lt;/li&gt;&#xA;&lt;li&gt;WAL虽然支持“一写多读”，而不是Journal文件那样的“一写全卡住”，但是还有一个问题没有解决：在做checkpoint操作的时候，连写事务也不能进行了。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;两个可能的优化方案&#34;&gt;&#xA;  两个可能的优化方案&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e4%b8%a4%e4%b8%aa%e5%8f%af%e8%83%bd%e7%9a%84%e4%bc%98%e5%8c%96%e6%96%b9%e6%a1%88&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;以下介绍sqlite目前在讨论的两个优化方案，之所以说是“可能”，因为看这部分代码还并没有合并到主干中，目前暂时还在分支里，参见：https://github.com/sqlite/sqlite/tree/begin-concurrent-pnu-wal2。&lt;/p&gt;&#xA;&lt;h3 class=&#34;heading&#34; id=&#34;wal-2&#34;&gt;&#xA;  WAL-2&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#wal-2&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;为了解决“checkpoint时无法进行写事务”的痛点，sqlite目前在尝试新的WAL-2机制。&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;figure class=&#34;&#34;&gt;&#xA;&#xA;    &lt;div&gt;&#xA;        &lt;img loading=&#34;lazy&#34; alt=&#34;wal-2&#34; src=&#34;https://www.codedump.info/media/imgs/20220904-weekly-24/wal-2.png&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; wal-2 &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;引入WAL-2之后，同时有两个WAL文件，这样可以：checkpoint其中一个WAL文件时，继续写另一个WAL文件，下一次再进行checkpoint时进行切换，这样checkpoint就不会阻塞住写操作。&lt;/p&gt;&#xA;&lt;h3 class=&#34;heading&#34; id=&#34;begin-concurrent&#34;&gt;&#xA;  BEGIN CONCURRENT&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#begin-concurrent&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;目前的WAL机制，都只能支持同一时间一个写事务，&lt;code&gt;BEGIN CONCURRENT&lt;/code&gt;机制可以实现多个写并发，这篇&lt;a href=&#34;https://www.sqlite.org/cgi/src/doc/begin-concurrent/doc/begin_concurrent.md&#34;&gt;SQLite: Begin Concurrent&lt;/a&gt;文档中，大概描述了一下这个优化的思路：&lt;/p&gt;</description>
    </item>
    <item>
      <title>sqlite3.36版本 btree实现（五）- Btree的实现</title>
      <link>https://www.codedump.info/zh/post/20220201-sqlite-btree-5-btree/</link>
      <pubDate>Tue, 01 Feb 2022 15:55:40 +0800</pubDate>
      <guid>https://www.codedump.info/zh/post/20220201-sqlite-btree-5-btree/</guid>
      <description>&lt;p&gt;《sqlite3.36版本 btree实现》系列文章：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.codedump.info/post/20211217-sqlite-btree-0/&#34;&gt;sqlite3.36版本 btree实现（零）- 起步及概述 - codedump的网络日志&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.codedump.info/post/20211217-sqlite-btree-1-pagecache/&#34;&gt;sqlite3.36版本 btree实现（一）- 管理页面缓存 - codedump的网络日志&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.codedump.info/post/20211218-sqlite-btree-2-concurrency-control/&#34;&gt;sqlite3.36版本 btree实现（二）- 并发控制框架 - codedump的网络日志&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.codedump.info/post/20211222-sqlite-btree-3-journal/&#34;&gt;sqlite3.36版本 btree实现（三）- journal文件备份机制 - codedump的网络日志&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.codedump.info/post/20220106-sqlite-btree-4-wal/&#34;&gt;sqlite3.36版本 btree实现（四）- WAL的实现 - codedump的网络日志&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.codedump.info/post/20220201-sqlite-btree-5-btree/&#34;&gt;sqlite3.36版本 btree实现（五）- Btree的实现 - codedump的网络日志&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;概述&#34;&gt;&#xA;  概述&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e6%a6%82%e8%bf%b0&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;前面的内容里，详细介绍了页面管理器部分的内容，回顾一下页面管理器和Btree模块的分工：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;页面管理器：提供页面级别的物理管理，如缓存、读取、写入、页面备份等。&lt;/li&gt;&#xA;&lt;li&gt;Btree：根据btree数据结构提供页面在逻辑上的组织，以及单个页面内的划分。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;还记得最开始，研究生产级别btree实现时的几个疑问：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;数据库教科书中，演示btree算法时，使用的都是定长的简单数据。实际应用中，存储的数据都是变长的，那么应该如何存储变长的数据呢？&lt;/li&gt;&#xA;&lt;li&gt;如果一行数据的大小，超过了一个物理页面的大小，又该如何处理？&lt;/li&gt;&#xA;&lt;li&gt;删除一行数据之后，它留下的空间如何回收利用？而回收利用时，不可避免的会出现碎片的问题，比如原先10字节的数据被回收，用来存储9字节的数据，多出来的1字节数据就被浪费了，碎片问题应该如何解决？&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;这些问题，都与“一个物理页面内数据如何组织”这个核心问题息息相关，带着这些问题展开btree实现的讨论。&lt;/p&gt;&#xA;&lt;p&gt;在下文中，不会讨论btree算法的细节，这部分不熟悉的，可以回看之前的文章或者教科书：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.codedump.info/post/20200609-btree-1/&#34;&gt;B树、B+树索引算法原理（上） - codedump的网络日志&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.codedump.info/post/20200615-btree-2/&#34;&gt;B树、B+树索引算法原理（下） - codedump的网络日志&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;物理页面的数据组织&#34;&gt;&#xA;  物理页面的数据组织&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e7%89%a9%e7%90%86%e9%a1%b5%e9%9d%a2%e7%9a%84%e6%95%b0%e6%8d%ae%e7%bb%84%e7%bb%87&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;数据表的逻辑组织和页面类型&#34;&gt;&#xA;  数据表的逻辑组织和页面类型&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e6%95%b0%e6%8d%ae%e8%a1%a8%e7%9a%84%e9%80%bb%e8%be%91%e7%bb%84%e7%bb%87%e5%92%8c%e9%a1%b5%e9%9d%a2%e7%b1%bb%e5%9e%8b&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;在展开具体的格式讨论之前，有必要先了解一下数据库文件的大体结构，已经不同的页面类型。&lt;/p&gt;&#xA;&lt;p&gt;sqlite中所谓的&lt;code&gt;数据库文件&lt;/code&gt;是单一文件，按照物理页面（2的次方）的大小来划分为多个页面。其中，每个表在数据库文件中是一棵btree的结构来组织，而不同类型的btree还区分了不同的页面。&lt;/p&gt;&#xA;&lt;p&gt;比如下图中，将平面的数据库文件，按照颜色划分成存储两个表的btree：&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;figure class=&#34;&#34;&gt;&#xA;&#xA;    &lt;div&gt;&#xA;        &lt;img loading=&#34;lazy&#34; alt=&#34;数据库文件的物理页面组织和逻辑页面结构&#34; src=&#34;https://www.codedump.info/media/imgs/20220201-sqlite-btree-5-btree/database-file.png&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; 数据库文件的物理页面组织和逻辑页面结构 &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;在上图中：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;上半部分表示，在物理的组织上，一个数据库文件以一个物理页面为基本单位来存储。&lt;/li&gt;&#xA;&lt;li&gt;下半部分表示，在逻辑的组织上，不同的表都有自己的btree树形结构，这是物理页面在逻辑上的组织方式。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;因为每个表都有自己的btree树形结构，如果每个表都有一个对应的根页面编号，比如图中的两个表，对应的树形结构中，根节点所在的页面分别是1和2。&lt;/p&gt;&#xA;&lt;p&gt;接着来看不同的页面类型，以及存储上的差异。&lt;/p&gt;&#xA;&lt;p&gt;以一个例子来说明，创建以下的数据库，插入数据，以及索引：&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;// 创建数据库COMPANY&#xA;CREATE TABLE COMPANY(&#xA;   ID             INT      NOT NULL,&#xA;   NAME           TEXT    NOT NULL,&#xA;   AGE            INT     NOT NULL,&#xA;   ADDRESS        CHAR(50),&#xA;   SALARY         REAL&#xA;);&#xA;&#xA;// 创建索引&#xA;CREATE INDEX id_index ON COMPANY (id);&#xA;&#xA;// 插入2条数据&#xA;INSERT INTO COMPANY (ID,NAME,AGE,ADDRESS,SALARY) VALUES (1, &amp;#39;Paul&amp;#39;, 32, &amp;#39;California&amp;#39;, 20000.00 );&#xA;INSERT INTO COMPANY (ID,NAME,AGE,ADDRESS,SALARY)&#xA;VALUES (2, &amp;#39;Allen&amp;#39;, 25, &amp;#39;Texas&amp;#39;, 15000.00 );&#xA;&#xA;// 查询数据&#xA;sqlite&amp;gt; select * from COMPANY;&#xA;1|Paul|32|California|20000.0&#xA;2|Allen|25|Texas|15000.0&#xA;&#xA;// 查询rowid和数据&#xA;sqlite&amp;gt; select rowid,* from COMPANY;&#xA;1|1|Paul|32|California|20000.0&#xA;2|2|Allen|25|Texas|15000.0&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;在上面的流程里：&lt;/p&gt;</description>
    </item>
    <item>
      <title>sqlite3.36版本 btree实现（四）- WAL的实现</title>
      <link>https://www.codedump.info/zh/post/20220106-sqlite-btree-4-wal/</link>
      <pubDate>Thu, 06 Jan 2022 21:48:18 +0800</pubDate>
      <guid>https://www.codedump.info/zh/post/20220106-sqlite-btree-4-wal/</guid>
      <description>&lt;p&gt;《sqlite3.36版本 btree实现》系列文章：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.codedump.info/post/20211217-sqlite-btree-0/&#34;&gt;sqlite3.36版本 btree实现（零）- 起步及概述 - codedump的网络日志&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.codedump.info/post/20211217-sqlite-btree-1-pagecache/&#34;&gt;sqlite3.36版本 btree实现（一）- 管理页面缓存 - codedump的网络日志&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.codedump.info/post/20211218-sqlite-btree-2-concurrency-control/&#34;&gt;sqlite3.36版本 btree实现（二）- 并发控制框架 - codedump的网络日志&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.codedump.info/post/20211222-sqlite-btree-3-journal/&#34;&gt;sqlite3.36版本 btree实现（三）- journal文件备份机制 - codedump的网络日志&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.codedump.info/post/20220106-sqlite-btree-4-wal/&#34;&gt;sqlite3.36版本 btree实现（四）- WAL的实现 - codedump的网络日志&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.codedump.info/post/20220201-sqlite-btree-5-btree/&#34;&gt;sqlite3.36版本 btree实现（五）- Btree的实现 - codedump的网络日志&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;概述&#34;&gt;&#xA;  概述&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e6%a6%82%e8%bf%b0&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;前面两节，分别讲解了sqlite中写入事务时的并发控制框架，以及journal备份文件的实现机制。&lt;/p&gt;&#xA;&lt;p&gt;回忆一下journal备份文件的实现：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;每次一个新的写事务开始之前，要首先写journal文件的文件头。&lt;/li&gt;&#xA;&lt;li&gt;写事务过程中，如果修改了哪个页面，在修改之前需要首先将这个页面的内容写入到journal文件中。&lt;/li&gt;&#xA;&lt;li&gt;写事务完成后，在同步所有缓存中被修改的页面到数据库文件之前，要首先将journal文件中的所有修改同步到磁盘，然后再修改数据库文件。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;可以看到，journal备份的整个流程都较为原始，性能不高，所以在sqlite 3.7.0版本（&lt;a href=&#34;https://www.sqlite.org/releaselog/3_7_0.html&#34;&gt;SQLite Release 3.7.0 On 2010-07-21&lt;/a&gt;，2010-07-21）中，引入了另一种备份机制：WAL（Write Ahead Log）。&lt;/p&gt;&#xA;&lt;p&gt;本节首先介绍WAL的实现原理，然后再展开其具体的实现。&lt;/p&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;wal工作原理&#34;&gt;&#xA;  WAL工作原理&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#wal%e5%b7%a5%e4%bd%9c%e5%8e%9f%e7%90%86&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;从前面journal的实现中可以看到，写入journal文件中的内容，是待修改页面修改之前的内容，而WAL则相反：被修改的页面内容首先写入到WAL中。&lt;/p&gt;&#xA;&lt;p&gt;用sqlite官网的文字来说，WAL文件的定义是这样的：&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;The write-ahead log or &amp;ldquo;wal&amp;rdquo; file is a roll-forward journal that records transactions that have been committed but not yet applied to the main database.&lt;/p&gt;</description>
    </item>
    <item>
      <title>sqlite3.36版本 btree实现（三）- journal文件备份机制</title>
      <link>https://www.codedump.info/zh/post/20211222-sqlite-btree-3-journal/</link>
      <pubDate>Wed, 22 Dec 2021 19:15:31 +0800</pubDate>
      <guid>https://www.codedump.info/zh/post/20211222-sqlite-btree-3-journal/</guid>
      <description>&lt;p&gt;《sqlite3.36版本 btree实现》系列文章：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.codedump.info/post/20211217-sqlite-btree-0/&#34;&gt;sqlite3.36版本 btree实现（零）- 起步及概述 - codedump的网络日志&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.codedump.info/post/20211217-sqlite-btree-1-pagecache/&#34;&gt;sqlite3.36版本 btree实现（一）- 管理页面缓存 - codedump的网络日志&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.codedump.info/post/20211218-sqlite-btree-2-concurrency-control/&#34;&gt;sqlite3.36版本 btree实现（二）- 并发控制框架 - codedump的网络日志&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.codedump.info/post/20211222-sqlite-btree-3-journal/&#34;&gt;sqlite3.36版本 btree实现（三）- journal文件备份机制 - codedump的网络日志&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.codedump.info/post/20220106-sqlite-btree-4-wal/&#34;&gt;sqlite3.36版本 btree实现（四）- WAL的实现 - codedump的网络日志&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.codedump.info/post/20220201-sqlite-btree-5-btree/&#34;&gt;sqlite3.36版本 btree实现（五）- Btree的实现 - codedump的网络日志&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;概述&#34;&gt;&#xA;  概述&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e6%a6%82%e8%bf%b0&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;在上一节中（&lt;a href=&#34;https://www.codedump.info/post/20211218-sqlite-btree-2-concurrency-control/&#34;&gt;sqlite3.36版本 btree实现（二）- 并发控制框架&lt;/a&gt;），已经讲解了sqlite中的并发控制机制，里面会涉及到一个“备份页面”的模块：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;备份所有在一个事务中会修改到的页面。&lt;/li&gt;&#xA;&lt;li&gt;出错时回滚页面内容。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;里面也提到，有两种备份文件的机制：journal文件，以及WAL文件。今天首先讲解journal文件的实现，它的效率会更低一些，也正是因为这个原因后续推出了更优的WAL机制。&lt;/p&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;相关命令&#34;&gt;&#xA;  相关命令&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e7%9b%b8%e5%85%b3%e5%91%bd%e4%bb%a4&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;sqlite中，可以使用&lt;code&gt;PRAGMA journal_mode&lt;/code&gt;来修改备份文件机制，包括以下几种：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;delete：默认模式。在该模式下，在事务结束时，备份文件将被删除。&lt;/li&gt;&#xA;&lt;li&gt;truncate：日志文件被截断为零字节长度。&lt;/li&gt;&#xA;&lt;li&gt;persist：日志文件被留在原地，但头部被重写，表明日志不再有效。&lt;/li&gt;&#xA;&lt;li&gt;memory：日志记录保留在内存中，而不是磁盘上。&lt;/li&gt;&#xA;&lt;li&gt;off：不保留任何备份记录。&lt;/li&gt;&#xA;&lt;li&gt;wal：采用wal形式的备份文件。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;其中，前面三种delete、truncate、persist都是使用journal文件来实现的备份，区别在于事务结束之后的对备份文件的处理罢了。&lt;/p&gt;&#xA;&lt;p&gt;本节首先讲解journal文件，下一节讲解wal备份文件。&lt;/p&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;journal文件格式&#34;&gt;&#xA;  journal文件格式&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#journal%e6%96%87%e4%bb%b6%e6%a0%bc%e5%bc%8f&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;journal文件的文件名规则是：与同目录的数据库文件同名，但是多了字符串“-journal”为后缀。比如数据库文件是“test.db”，那么对应的journal文件名为“test.db-journal”。&lt;/p&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;文件头&#34;&gt;&#xA;  文件头&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e6%96%87%e4%bb%b6%e5%a4%b4&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;偏移量&lt;/th&gt;&#xA;          &lt;th&gt;大小&lt;/th&gt;&#xA;          &lt;th&gt;描述&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;0&lt;/td&gt;&#xA;          &lt;td&gt;8&lt;/td&gt;&#xA;          &lt;td&gt;文件头的magic number: 0xd9, 0xd5, 0x05, 0xf9, 0x20, 0xa1, 0x63, 0xd7&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;8&lt;/td&gt;&#xA;          &lt;td&gt;4&lt;/td&gt;&#xA;          &lt;td&gt;journal文件中的页面数量，如果为-1表示一直到journal文件尾&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;12&lt;/td&gt;&#xA;          &lt;td&gt;4&lt;/td&gt;&#xA;          &lt;td&gt;每次计算校验值时算出来的随机数&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;16&lt;/td&gt;&#xA;          &lt;td&gt;4&lt;/td&gt;&#xA;          &lt;td&gt;在开始备份前数据库文件的页面数量&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;20&lt;/td&gt;&#xA;          &lt;td&gt;4&lt;/td&gt;&#xA;          &lt;td&gt;磁盘扇区大小&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;24&lt;/td&gt;&#xA;          &lt;td&gt;4&lt;/td&gt;&#xA;          &lt;td&gt;journal文件中的页面大小&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;这里大部分的字段都自解释了，不必多做解释，唯一需要注意的是随机数，因为这是用来后续校验备份页面的字段，这将在后面结合流程来说明。&lt;/p&gt;</description>
    </item>
    <item>
      <title>sqlite3.36版本 btree实现（二）- 并发控制框架</title>
      <link>https://www.codedump.info/zh/post/20211218-sqlite-btree-2-concurrency-control/</link>
      <pubDate>Sat, 18 Dec 2021 15:25:05 +0800</pubDate>
      <guid>https://www.codedump.info/zh/post/20211218-sqlite-btree-2-concurrency-control/</guid>
      <description>&lt;p&gt;《sqlite3.36版本 btree实现》系列文章：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.codedump.info/post/20211217-sqlite-btree-0/&#34;&gt;sqlite3.36版本 btree实现（零）- 起步及概述 - codedump的网络日志&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.codedump.info/post/20211217-sqlite-btree-1-pagecache/&#34;&gt;sqlite3.36版本 btree实现（一）- 管理页面缓存 - codedump的网络日志&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.codedump.info/post/20211218-sqlite-btree-2-concurrency-control/&#34;&gt;sqlite3.36版本 btree实现（二）- 并发控制框架 - codedump的网络日志&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.codedump.info/post/20211222-sqlite-btree-3-journal/&#34;&gt;sqlite3.36版本 btree实现（三）- journal文件备份机制 - codedump的网络日志&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.codedump.info/post/20220106-sqlite-btree-4-wal/&#34;&gt;sqlite3.36版本 btree实现（四）- WAL的实现 - codedump的网络日志&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.codedump.info/post/20220201-sqlite-btree-5-btree/&#34;&gt;sqlite3.36版本 btree实现（五）- Btree的实现 - codedump的网络日志&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;概述&#34;&gt;&#xA;  概述&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e6%a6%82%e8%bf%b0&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;按照之前起步阶段对sqlite btree整体架构的分析，“页面管理模块”分为以下几个子模块：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;页面缓存管理。&lt;/li&gt;&#xA;&lt;li&gt;页面备份，又分为以下两种实现：&#xA;&lt;ul&gt;&#xA;&lt;li&gt;journal文件。&lt;/li&gt;&#xA;&lt;li&gt;WAL文件。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;页面管理模块。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;前面一节讲完了“页面缓存管理”的实现，按照自下往上的顺序，就应该到“页面备份”了。“页面备份”核心的工作是：在真正修改页面内容之前，将还未修改的页面内容备份，这样一旦系统在事务过程中宕机崩溃，就可以用这部分内容回滚还未落盘的事务修改，让系统回到一个正确的状态。&lt;/p&gt;&#xA;&lt;p&gt;“页面备份”有两种实现方式，在早期使用的journal文件，这种方式性能不高；在3.7版本之后，sqlite引入了WAL文件来保存页面内容，这样做的效率更高。&lt;/p&gt;&#xA;&lt;p&gt;本节就讲解这部分内容，在对这部分内容有一个总体的了解之后，继续讲解页面备份的总体流程。后面的章节再具体分析journal以及WAL的实现。&lt;/p&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;写事务的流程&#34;&gt;&#xA;  写事务的流程&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e5%86%99%e4%ba%8b%e5%8a%a1%e7%9a%84%e6%b5%81%e7%a8%8b&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;（以下流程分析，按照sqlite官网中的文档&lt;a href=&#34;https://sqlite.org/atomiccommit.html&#34;&gt;Atomic Commit In SQLite&lt;/a&gt;进行讲解，图例也全部引用自官网。）&lt;/p&gt;&#xA;&lt;p&gt;sqlite的写事务，分为以下几个流程：&lt;/p&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;1初始化阶段initial-state&#34;&gt;&#xA;  1、初始化阶段（Initial State）&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#1%e5%88%9d%e5%a7%8b%e5%8c%96%e9%98%b6%e6%ae%b5initial-state&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;figure class=&#34;&#34;&gt;&#xA;&#xA;    &lt;div&gt;&#xA;        &lt;img loading=&#34;lazy&#34; alt=&#34;初始化&#34; src=&#34;https://www.codedump.info/media/imgs/20211218-sqlite-btree-2-concurrency-control/commit-0.gif&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; 初始化 &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;如上图中，从右到左即是系统的磁盘、操作系统缓冲区、用户空间三部分，其中磁盘和操作系统缓冲区有划分为多块的空间，每一块在sqlite里被称为一个&lt;code&gt;sector&lt;/code&gt;，蓝色部分表示是修改之前的数据。&lt;/p&gt;&#xA;&lt;p&gt;这是系统初始时的样子。&lt;/p&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;2拿到读锁acquiring-a-read-lock&#34;&gt;&#xA;  2、拿到读锁（Acquiring A Read Lock）&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#2%e6%8b%bf%e5%88%b0%e8%af%bb%e9%94%81acquiring-a-read-lock&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;figure class=&#34;&#34;&gt;&#xA;&#xA;    &lt;div&gt;&#xA;        &lt;img loading=&#34;lazy&#34; alt=&#34;拿到读锁&#34; src=&#34;https://www.codedump.info/media/imgs/20211218-sqlite-btree-2-concurrency-control/commit-1.gif&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; 拿到读锁 &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;在开始进行写操作之前，sqlite必须先把待修改的页面加载内存中（这就是上一节“页面缓存管理器”做的事情），后续的修改其实也是首先修改这部分加载到内存中的页面内容，因为可能一次提交会修改同一个页面中的多处内容，最后才把页面内容落盘。&lt;/p&gt;&#xA;&lt;p&gt;所以，这一步所要做的，是首先拿到数据库文件的读锁（shared lock），需要说明的是，这个读锁是数据库级别的锁。同一时间，系统中可以存在多个读锁，但是只要系统中还存在读锁，就不再允许分配出新的写锁（write lock）。&lt;/p&gt;</description>
    </item>
    <item>
      <title>sqlite3.36版本 btree实现（一）- 管理页面缓存</title>
      <link>https://www.codedump.info/zh/post/20211217-sqlite-btree-1-pagecache/</link>
      <pubDate>Fri, 17 Dec 2021 14:22:06 +0800</pubDate>
      <guid>https://www.codedump.info/zh/post/20211217-sqlite-btree-1-pagecache/</guid>
      <description>&lt;p&gt;《sqlite3.36版本 btree实现》系列文章：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.codedump.info/post/20211217-sqlite-btree-0/&#34;&gt;sqlite3.36版本 btree实现（零）- 起步及概述 - codedump的网络日志&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.codedump.info/post/20211217-sqlite-btree-1-pagecache/&#34;&gt;sqlite3.36版本 btree实现（一）- 管理页面缓存 - codedump的网络日志&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.codedump.info/post/20211218-sqlite-btree-2-concurrency-control/&#34;&gt;sqlite3.36版本 btree实现（二）- 并发控制框架 - codedump的网络日志&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.codedump.info/post/20211222-sqlite-btree-3-journal/&#34;&gt;sqlite3.36版本 btree实现（三）- journal文件备份机制 - codedump的网络日志&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.codedump.info/post/20220106-sqlite-btree-4-wal/&#34;&gt;sqlite3.36版本 btree实现（四）- WAL的实现 - codedump的网络日志&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.codedump.info/post/20220201-sqlite-btree-5-btree/&#34;&gt;sqlite3.36版本 btree实现（五）- Btree的实现 - codedump的网络日志&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;概述&#34;&gt;&#xA;  概述&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e6%a6%82%e8%bf%b0&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;&lt;code&gt;页面管理&lt;/code&gt;模块中，很重要的一个功能是缓存页面的内容在内存中：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;读页面：如果页面已经在内存，就不需要到文件中读出页面内容。&lt;/li&gt;&#xA;&lt;li&gt;写页面：如果页面已经在内存，那么对页面的修改就只需要修改页面在内存中的数据即可，被修改了但是还没有落盘的页面，被称为“脏页面（dirty page）“。这样，多次对某个页面的修改，可能最后只需要一次落盘即可。当然，对页面的修改，如果在还没有落盘之前，系统就崩溃了，这种情况下应该如何处理，这就是“崩溃恢复”模块做的事情了。本节中，将专注在“页面缓存”这个子模块的实现。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;既然要将页面缓存在内存中，就会涉及到几个功能：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;如何知道哪些页面已经被缓存在内存里了？&lt;/li&gt;&#xA;&lt;li&gt;缓存在内存中的页面如何组织管理？&lt;/li&gt;&#xA;&lt;li&gt;缓存页面使用的内存不够用时，应该如何处理？&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;我们首先来了解一下“页面缓存”模块的总体划分：&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;figure class=&#34;&#34;&gt;&#xA;&#xA;    &lt;div&gt;&#xA;        &lt;img loading=&#34;lazy&#34; alt=&#34;页面缓存功能的模块划分&#34; src=&#34;https://www.codedump.info/media/imgs/20211217-sqlite-btree-1-pagecache/pagecache.png&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; 页面缓存功能的模块划分 &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;按照上图的划分，页面缓存模块分为以下几部分：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;页面缓存管理器：实现了页面缓存的总体算法流程，以及提供对外的接口，但是具体到“页面缓存算法”的实现，则有赖于下面这个可用户定制的&lt;code&gt;sqlite3_pcache_methods2&lt;/code&gt;。这部分功能在代码&lt;code&gt;pcache.c&lt;/code&gt;中。&lt;/li&gt;&#xA;&lt;li&gt;页面缓存算法：用户可自己定制，只要实现&lt;code&gt;sqlite3_pcache_methods2&lt;/code&gt;结构体中的接口即可。系统中的默认实现，在文件&lt;code&gt;pcache1.c&lt;/code&gt;中。&lt;/li&gt;&#xA;&lt;li&gt;除此以外，还需要快速根据页面编号就能知道哪些页面已经被缓存的功能，这部分sqlite使用位图数据结构来实现，在文件&lt;code&gt;bitvec.c&lt;/code&gt;中。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;页面缓存管理器，核心功能就是维护脏页面链表，缓存页面的管理，诸如根据页面编号查找页面、淘汰页面算法等，都由“页面缓存算法”来维护。可以这样来简单的理解上面的功能划分：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;“页面缓存管理器”：定义了管理页面缓存的接口、总体流程，维护管理目前在用的脏页面。&lt;/li&gt;&#xA;&lt;li&gt;“页面缓存算法”：维护其它不在使用但还在内存中的页面，负责其淘汰、回收等实现。由“sqlite3_pcache_methods2”结构体实现，用户可以定制自己实现的“sqlite3_pcache_methods2”，系统也提供默认的实现。当内存不足以分配时，需要淘汰不常用的页面，这时候需要使用“页面缓存管理器”注册的回调函数来淘汰页面。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;figure class=&#34;&#34;&gt;&#xA;&#xA;    &lt;div&gt;&#xA;        &lt;img loading=&#34;lazy&#34; alt=&#34;页面缓存两个模块的功能划分&#34; src=&#34;https://www.codedump.info/media/imgs/20211217-sqlite-btree-1-pagecache/pagecache_module.png&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; 页面缓存两个模块的功能划分 &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;简而言之，如果把当前在内存中的页面划分为以下两类，那么：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;当前在使用的页面：即与页面编号对应的页面，由“页面缓存管理器”维护。&lt;/li&gt;&#xA;&lt;li&gt;当前还未使用、但也在内存中的页面：即随时准备拿出来存储从磁盘中读出来的数据的页面，由“页面缓存算法”维护，比如淘汰、回收、复用等。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;figure class=&#34;&#34;&gt;&#xA;&#xA;    &lt;div&gt;&#xA;        &lt;img loading=&#34;lazy&#34; alt=&#34;两种页面的划分&#34; src=&#34;https://www.codedump.info/media/imgs/20211217-sqlite-btree-1-pagecache/page_cache_memory.png&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; 两种页面的划分 &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;下面，就开始“页面缓存”这几部分功能的具体讲解。&lt;/p&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;管理页面&#34;&gt;&#xA;  管理页面&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e7%ae%a1%e7%90%86%e9%a1%b5%e9%9d%a2&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;页面相关的数据数据结构&#34;&gt;&#xA;  页面相关的数据数据结构&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e9%a1%b5%e9%9d%a2%e7%9b%b8%e5%85%b3%e7%9a%84%e6%95%b0%e6%8d%ae%e6%95%b0%e6%8d%ae%e7%bb%93%e6%9e%84&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;首先来看页面相关的数据结构，sqlite中使用&lt;code&gt;PgHdr&lt;/code&gt;结构体来在内存中描述一个页面：&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;/*&#xA;** Every page in the cache is controlled by an instance of the following&#xA;** structure.&#xA;*/&#xA;struct PgHdr {&#xA;  sqlite3_pcache_page *pPage;    /* Pcache object page handle */&#xA;  void *pData;                   /* Page data */&#xA;  void *pExtra;                  /* Extra content */&#xA;  PCache *pCache;                /* PRIVATE: Cache that owns this page */&#xA;  PgHdr *pDirty;                 /* Transient list of dirty sorted by pgno */&#xA;  Pager *pPager;                 /* The pager this page is part of */&#xA;  Pgno pgno;                     /* Page number for this page */&#xA;#ifdef SQLITE_CHECK_PAGES&#xA;  u32 pageHash;                  /* Hash of page content */&#xA;#endif&#xA;  u16 flags;                     /* PGHDR flags defined below */&#xA;&#xA;  /**********************************************************************&#xA;  ** Elements above, except pCache, are public.  All that follow are &#xA;  ** private to pcache.c and should not be accessed by other modules.&#xA;  ** pCache is grouped with the public elements for efficiency.&#xA;  */&#xA;  i16 nRef;                      /* Number of users of this page */&#xA;  PgHdr *pDirtyNext;             /* Next element in list of dirty pages */&#xA;  PgHdr *pDirtyPrev;             /* Previous element in list of dirty pages */&#xA;                          /* NB: pDirtyNext and pDirtyPrev are undefined if the&#xA;                          ** PgHdr object is not dirty */&#xA;};&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;其中的信息，大部分在注释中已经自解释：&lt;/p&gt;</description>
    </item>
    <item>
      <title>sqlite3.36版本 btree实现（零）- 起步及概述</title>
      <link>https://www.codedump.info/zh/post/20211217-sqlite-btree-0/</link>
      <pubDate>Fri, 17 Dec 2021 10:19:05 +0800</pubDate>
      <guid>https://www.codedump.info/zh/post/20211217-sqlite-btree-0/</guid>
      <description>&lt;p&gt;《sqlite3.36版本 btree实现》系列文章：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.codedump.info/post/20211217-sqlite-btree-0/&#34;&gt;sqlite3.36版本 btree实现（零）- 起步及概述 - codedump的网络日志&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.codedump.info/post/20211217-sqlite-btree-1-pagecache/&#34;&gt;sqlite3.36版本 btree实现（一）- 管理页面缓存 - codedump的网络日志&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.codedump.info/post/20211218-sqlite-btree-2-concurrency-control/&#34;&gt;sqlite3.36版本 btree实现（二）- 并发控制框架 - codedump的网络日志&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.codedump.info/post/20211222-sqlite-btree-3-journal/&#34;&gt;sqlite3.36版本 btree实现（三）- journal文件备份机制 - codedump的网络日志&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.codedump.info/post/20220106-sqlite-btree-4-wal/&#34;&gt;sqlite3.36版本 btree实现（四）- WAL的实现 - codedump的网络日志&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.codedump.info/post/20220201-sqlite-btree-5-btree/&#34;&gt;sqlite3.36版本 btree实现（五）- Btree的实现 - codedump的网络日志&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;起步&#34;&gt;&#xA;  起步&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e8%b5%b7%e6%ad%a5&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;在去年大体把btree以及b+tree算法流程研究了之后，我写了两篇博客：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.codedump.info/post/20200609-btree-1/&#34;&gt;B树、B+树索引算法原理（上） - codedump的网络日志&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.codedump.info/post/20200615-btree-2/&#34;&gt;B树、B+树索引算法原理（下） - codedump的网络日志&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;（鉴于b+tree只是btree的一个特例，下面描述将仅使用“btree”，不再严格区分两者。）&lt;/p&gt;&#xA;&lt;p&gt;但是，这两篇文章仅仅只是让我懂得了最基本的原理。懂得原理，只是能做出toy级别的实现，拿btree类的存储引擎来说，要做到生产级产品，至少还有以下几个问题我当时不知道怎么做的：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;如何处理不同大小的数据的存储？&lt;/li&gt;&#xA;&lt;li&gt;删除一个数据之后，如何复用其留下的空间？&lt;/li&gt;&#xA;&lt;li&gt;错误、崩溃恢复怎么做？&lt;/li&gt;&#xA;&lt;li&gt;跟磁盘文件是如何交互的？&lt;/li&gt;&#xA;&lt;li&gt;页面缓存模块如何实现？&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;等等等等，还有太多我还没有弄清楚的实现细节。&lt;/p&gt;&#xA;&lt;p&gt;（我甚至还在微博上发问，得到了两个质量很高的回答，见本文最后的&lt;a href=&#34;https://www.codedump.info/post/20211217-sqlite-btree-0/#%E5%BD%A9%E8%9B%8B&#34;&gt;彩蛋部分&lt;/a&gt;。）&lt;/p&gt;&#xA;&lt;p&gt;对LSM类存储引擎有了解的人都知道，Leveldb这个项目在LSM领域属于入门级别的生产级实现，即这个领域最精简、但是又能放心在某些要求不高的场景下用于生产的项目。在这之后，我一直在找那种btree领域的“leveldb”，很遗憾一直都没有找到，我分别看了目前WiredTiger、innodb、sqlite的对应实现，都太复杂了，看不下去。&lt;/p&gt;&#xA;&lt;p&gt;直到有一天，无意间发现了这个项目：&lt;a href=&#34;https://github.com/madushadhanushka/simple-sqlite&#34;&gt;madushadhanushka/simple-sqlite: Code reading for sqlite backend&lt;/a&gt;，看介绍，作者把sqlite2.5里b-tree相关的部分代码抽取出来了，我编译运行了一下用例都能正常跑，代码量不过几千行，我只花了几天就看完了。&lt;/p&gt;&#xA;&lt;p&gt;虽然按照&lt;a href=&#34;https://www.sqlite.org/changes.html&#34;&gt;Release History Of SQLite&lt;/a&gt;上的记载，sqlite 2.5版本是2002年的版本了，但是这个版本还是某种程度回答了我在上面的疑问。&lt;/p&gt;&#xA;&lt;p&gt;趁热打铁，我又找来更新一些的sqlite 3.6.10代码继续看这部分的实现，这次花了更多的时间才看完，但是又增强了我的信心。由于这个版本的sqlite，还未实现btree的wal，还只是用了journal文件来做崩溃恢复（无论wal还是journal，都会在后面文章展开详细讨论），所以在有足够的信心之后，我接下来又继续看当时（2021.10月份）最新的sqlite 3.36版本的实现，这部分的实现对比3.6.10来说，在btree部分最大的变化就是多了wal的实现，在已经清楚3.6.10的前提下，再增加了解这部分的实现，也并不是什么难事了。&lt;/p&gt;&#xA;&lt;p&gt;以上，简单描述了我探索一个生产级btree实现的初过程，btree类存储引擎的实现博大精深，更复杂者还有很多（WiredTiger、innodb、tokudb&amp;hellip;），但是无疑从低版本sqlite开始的探索流程，终于让我打开了走上这条路的一扇大门。&lt;/p&gt;&#xA;&lt;p&gt;本系列文章就sqlite 3.36版本的btree实现展开描述，希望对那些和我一样对“生产级btree类存储引擎实现”有好奇心的人有一点帮助。&lt;/p&gt;&#xA;&lt;p&gt;当然，如果你还是觉得吃力，可以先从&lt;a href=&#34;https://github.com/madushadhanushka/simple-sqlite&#34;&gt;madushadhanushka/simple-sqlite: Code reading for sqlite backend&lt;/a&gt;这里看起。这里并不建议对btree原理没有了解的人直接上手sqlite的实现，如果需要了解原理请参考相关文章或者我上面给出的我写的两篇博客。这系列文章中，将不再对btree原理做过多描述，将假设读者已经了解这部分内容。&lt;/p&gt;</description>
    </item>
  </channel>
</rss>
