<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>存储 on codedump notes</title>
    <link>https://www.codedump.info/zh/tags/%E5%AD%98%E5%82%A8/</link>
    <description>Recent content in 存储 on codedump notes</description>
    <generator>Hugo</generator>
    <language>zh</language>
    <lastBuildDate>Tue, 01 Feb 2022 15:55:40 +0800</lastBuildDate>
    <atom:link href="https://www.codedump.info/zh/tags/%E5%AD%98%E5%82%A8/index.xml" rel="self" type="application/rss+xml" />
    <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>
    <item>
      <title>Memcached的存储原理解析（续）</title>
      <link>https://www.codedump.info/zh/post/20210812-memcached/</link>
      <pubDate>Thu, 12 Aug 2021 08:40:04 +0800</pubDate>
      <guid>https://www.codedump.info/zh/post/20210812-memcached/</guid>
      <description>&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/20210701-memcached/&#34;&gt;Memcached的存储原理解析&lt;/a&gt;一文中，简单分析了memcached的存储原理，但是最近在照搬memcached的实现原理到项目中时，发现前面的梳理还不够细致，有一些细节没有谈及，因此重新整理一篇文章。&lt;/p&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;slab&#34;&gt;&#xA;  slab&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#slab&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;memcached是根据slab为基础单位来管理空闲空间的。slab的大体原理如下图：&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;slabclass的分级存储&#34; src=&#34;https://www.codedump.info/media/imgs/20210812-memcached/slabclass.png&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; slabclass的分级存储 &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;slabs.c中定义了类型为&lt;code&gt;slabclass_t&lt;/code&gt;、大小为&lt;code&gt;MAX_NUMBER_OF_SLAB_CLASSES&lt;/code&gt;的数组&lt;code&gt;slabclass&lt;/code&gt;，用于分级存储。&lt;/p&gt;&#xA;&lt;p&gt;数组中的每个&lt;code&gt;slabclass_t&lt;/code&gt;元素，其能分配出去的内存大小递增，由如下的规则决定：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;每个数组可分配的内存大小都要8字节对齐（&lt;code&gt;CHUNK_ALIGN_BYTES&lt;/code&gt;）,这个大小保存在&lt;code&gt;slabclass_t&lt;/code&gt;的&lt;code&gt;size&lt;/code&gt;成员中。&lt;/li&gt;&#xA;&lt;li&gt;数组的第一个&lt;code&gt;slabclass_t&lt;/code&gt;元素的可分配内存大小为&lt;code&gt;sizeof(item) + settings.chunk_size&lt;/code&gt;。这之后的&lt;code&gt;slabclass_t&lt;/code&gt;可分配内存大小，都在上一个的元素的基础上放大&lt;code&gt;factor&lt;/code&gt;倍，同时还要8字节对齐。&lt;/li&gt;&#xA;&lt;li&gt;每次分配一个页面的大小由配置项&lt;code&gt;settings.slab_page_size&lt;/code&gt;来决定，因此每一个&lt;code&gt;slabclass_t&lt;/code&gt;元素的一个页面能容纳的&lt;code&gt;item&lt;/code&gt;数量为&lt;code&gt;settings.slab_page_size / slabclass[i].size&lt;/code&gt;。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;以上图为例，假设第一级存储的item大小不超过56字节，每个slab之间的增长因子是1.2，那么下一个slab存储的item内存大小就是56*1.2=72字节。&lt;/p&gt;&#xA;&lt;p&gt;在当前还有空闲可用内存的情况下，每一次分配新的空间，都是以page（page=1MB）为单位的，然后再根据该slab的item大小划分为多个空闲可用item。&lt;/p&gt;&#xA;&lt;p&gt;&lt;code&gt;slabclass_t&lt;/code&gt;类型中最重要的是以下两个成员：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;slab_list：保存已经分配出去的page数组，分配一个page的内存之后，需要将page根据该slab的size划分成多个空闲的item，挂载到下面提到的slots链表中。当最后需要回收分配出去的内存时，直接遍历slab_list中的成员回收内存即可。&lt;/li&gt;&#xA;&lt;li&gt;slots：保存空闲item链表。空闲item来源有两部分，一部分是从page中分配但是还未使用的item，还有一部分是曾经被使用后来释放回来的item，上图中使用了不同的颜色进行了区分。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;当需要分配一块大小的内存时，首先需要根据其大小，计算出该尺寸最终对应到上面的哪个元素，这个数组索引在Memcached中被称为&lt;code&gt;clsid&lt;/code&gt;，这个计算索引的过程参见函数&lt;code&gt;slabs_clsid&lt;/code&gt;。&lt;/p&gt;&#xA;&lt;p&gt;比如：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;slabclass[0].size = 56，fator参数为1.2，那么slabclass[1].size = (56 * 1.25)向上对齐8位 = 72，以此类推。&lt;/li&gt;&#xA;&lt;li&gt;假设需要分配的内存大小为60，就会去找&lt;code&gt;slabclass_t.size &amp;gt;= 60&lt;/code&gt;的第一个slabclass，在这个例子中返回的&lt;code&gt;clsid&lt;/code&gt;是1，也就是&lt;code&gt;slabclass[1]&lt;/code&gt;。&lt;/li&gt;&#xA;&lt;li&gt;内存分配时根据大小向上取满足条件的第一个slab的做法，优点在于方便了内存的分配管理，缺陷是会浪费掉部分空间，比如上面的例子中，将大小为72的slab用于60的内存，那么12字节的空间就被浪费掉了。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;从上面可以看到，&lt;code&gt;slabclass_t&lt;/code&gt;用于管理空闲内存，当需要分配新item时，会依次做如下的检查：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;如果&lt;code&gt;slots&lt;/code&gt;链表中还有空闲item，直接摘下来使用；&lt;/li&gt;&#xA;&lt;li&gt;否则，如果当前还没有达到内存分配的阈值，就分配一个新的page出来，将page按照该slab的大小划分为多个item，这些新分配出来的item都挂载到&lt;code&gt;slots&lt;/code&gt;链表中。&lt;/li&gt;&#xA;&lt;li&gt;如果以上两步都不满足了，说明当前已经没有可用的内存和空闲item，需要进行淘汰了。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;讲到item的淘汰，就涉及到下面的LRU算法了。&lt;/p&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;lru算法&#34;&gt;&#xA;  LRU算法&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#lru%e7%ae%97%e6%b3%95&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;旧的lru算法及其问题&#34;&gt;&#xA;  旧的LRU算法及其问题&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e6%97%a7%e7%9a%84lru%e7%ae%97%e6%b3%95%e5%8f%8a%e5%85%b6%e9%97%ae%e9%a2%98&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;以往的LRU算法，基本做法都是这样的：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;创建一个LRU链表，每次新加入的元素都放在链表头。&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;ul&gt;&#xA;&lt;li&gt;元素被访问一次就会被放到LRU链表的头部，这样即便这个元素可以被淘汰，也会需要很久才会淘汰掉这个元素。&lt;/li&gt;&#xA;&lt;li&gt;由于上面的原因，从链表尾部开始找可以淘汰的元素时，实际可能访问到的是一些虽然不常被访问，但是还没到淘汰时间（即有效时间TTL还未过期）的数据，这样会一直沿着链表往前找很久才能找到适合淘汰的元素。由于这个查找被淘汰元素的过程是需要加锁保护的，加锁时间一长影响了系统的并发。&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;经典的LRU链表实现&#34; src=&#34;https://www.codedump.info/media/imgs/20210701-memcached/old-lru-list.png&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; 经典的LRU链表实现 &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;综上，经典的LRU链表问题的核心在于：&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;从Memcached 1.5版本开始，引入了所谓的分段LRU算法（Segmented LRU）来解决这些问题。&lt;/p&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;改进的分段lru算法segmented-lru&#34;&gt;&#xA;  改进的分段LRU算法（Segmented LRU）&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e6%94%b9%e8%bf%9b%e7%9a%84%e5%88%86%e6%ae%b5lru%e7%ae%97%e6%b3%95segmented-lru&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;分段LRU算法中将LRU链表根据&lt;code&gt;活跃度&lt;/code&gt;分成了三类：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;HOT_LRU：存储热数据的LRU链表。&lt;/li&gt;&#xA;&lt;li&gt;WARM_LRU：存储温数据（即活跃度不如热数据）的LRU链表。&lt;/li&gt;&#xA;&lt;li&gt;COLD_LRU：存储冷数据的LRU链表。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;需要说明的是：热（参数&lt;code&gt;settings.hot_lru_pct&lt;/code&gt;）和暖（参数&lt;code&gt;settings.warm_lru_pct&lt;/code&gt;）数据的占总体内存的比例有限制，而冷数据则无限。&lt;/p&gt;</description>
    </item>
    <item>
      <title>Memcached的存储原理解析</title>
      <link>https://www.codedump.info/zh/post/20210701-memcached/</link>
      <pubDate>Thu, 01 Jul 2021 14:00:22 +0800</pubDate>
      <guid>https://www.codedump.info/zh/post/20210701-memcached/</guid>
      <description>&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;最近工作上的需要，需要做一个LRU形式管理内存的分配器，首先想到的就是Memcached这个项目。早些年粗略的看过一些，有个大体的了解，这一次看下来发现其LRU算法做了不少的改动。&lt;/p&gt;&#xA;&lt;p&gt;本文解析Memcached内存管理这部分的内容，基于Memcached 1.6.9版本。&lt;/p&gt;&#xA;&lt;p&gt;Memcached将单个KV数据的存储，都放在&lt;code&gt;item&lt;/code&gt;这个结构体中，每个&lt;code&gt;item&lt;/code&gt;数据同时存在于这几个数据结构之中：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;slabclass_t：以分级存储机制来提供内存的数据结构（下面展开详细讨论slabclass）。&lt;/li&gt;&#xA;&lt;li&gt;链表：当&lt;code&gt;item&lt;/code&gt;被使用时，存储在LRU链表中（下面详细讨论LRU链表）；当&lt;code&gt;item&lt;/code&gt;被释放之后，空闲的&lt;code&gt;item&lt;/code&gt;形成一个链表以备再次使用。&lt;/li&gt;&#xA;&lt;li&gt;hash表：用于根据键值查找数据的数据结构。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;hash表自不必多说，Memcached中将&lt;code&gt;item&lt;/code&gt;组织成一个名为&lt;code&gt;primary_hashtable&lt;/code&gt;的hash数组，根据键值查找元素时，首先计算出键值的hash值，再到对应的数组元素中遍历查找数据。&lt;/p&gt;&#xA;&lt;p&gt;&lt;code&gt;slabclass_t&lt;/code&gt;结构体以分级的方式分配内存给&lt;code&gt;item&lt;/code&gt;，这样做有以下几个好处：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;统一了内存的管理，避免了内存的碎片化。&lt;/li&gt;&#xA;&lt;li&gt;分配、释放内存时都能到对应的slab中。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;slabclass_t&#34;&gt;&#xA;  slabclass_t&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#slabclass_t&#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;#%e5%ae%9a%e4%b9%89&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;slabs.c中定义了类型为&lt;code&gt;slabclass_t&lt;/code&gt;、大小为&lt;code&gt;MAX_NUMBER_OF_SLAB_CLASSES&lt;/code&gt;的数组&lt;code&gt;slabclass&lt;/code&gt;，用于分级存储。&lt;/p&gt;&#xA;&lt;p&gt;数组中的每个&lt;code&gt;slabclass_t&lt;/code&gt;元素，其能分配出去的内存大小递增，由如下的规则决定：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;每个数组可分配的内存大小都要8字节对齐（&lt;code&gt;CHUNK_ALIGN_BYTES&lt;/code&gt;）,这个大小保存在&lt;code&gt;slabclass_t&lt;/code&gt;的&lt;code&gt;size&lt;/code&gt;成员中。&lt;/li&gt;&#xA;&lt;li&gt;数组的第一个&lt;code&gt;slabclass_t&lt;/code&gt;元素的可分配内存大小为&lt;code&gt;sizeof(item) + settings.chunk_size&lt;/code&gt;。这之后的&lt;code&gt;slabclass_t&lt;/code&gt;可分配内存大小，都在上一个的元素的基础上放大&lt;code&gt;factor&lt;/code&gt;倍，同时还要8字节对齐。&lt;/li&gt;&#xA;&lt;li&gt;每次分配一个页面的大小由配置项&lt;code&gt;settings.slab_page_size&lt;/code&gt;来决定，因此每一个&lt;code&gt;slabclass_t&lt;/code&gt;元素的一个页面能容纳的&lt;code&gt;item&lt;/code&gt;数量为&lt;code&gt;settings.slab_page_size / slabclass[i].size&lt;/code&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;slabclass的分级存储&#34; src=&#34;https://www.codedump.info/media/imgs/20210701-memcached/slabclass.png&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; slabclass的分级存储 &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;当需要分配一块大小的内存时，首先需要根据其大小，计算出该尺寸最终对应到上面的哪个元素，这个数组索引在Memcached中被称为&lt;code&gt;clsid&lt;/code&gt;，这个计算索引的过程参见函数&lt;code&gt;slabs_clsid&lt;/code&gt;。&lt;/p&gt;&#xA;&lt;p&gt;比如：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;slabclass[0].size = 56，fator参数为1.2，那么slabclass[1].size = (56 * 1.25)向上对齐8位 = 72，以此类推。&lt;/li&gt;&#xA;&lt;li&gt;假设需要分配的内存大小为60，就会去找&lt;code&gt;slabclass_t.size &amp;gt;= 60&lt;/code&gt;的第一个slabclass，在这个例子中返回的&lt;code&gt;clsid&lt;/code&gt;是1，也就是&lt;code&gt;slabclass[1]&lt;/code&gt;。&lt;/li&gt;&#xA;&lt;li&gt;内存分配时根据大小向上取满足条件的第一个slab的做法，优点在于方便了内存的分配管理，缺陷是会浪费掉部分空间，比如上面的例子中，将大小为72的slab用于60的内存，那么12字节的空间就被浪费掉了。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;每一个slab中，需要维持两类空间：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;按照页面大小来分配的一整页空间，每个页面又按照该slab的大小划分成了多个不同的chunk。&lt;/li&gt;&#xA;&lt;li&gt;管理使用已被释放的item。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;在&lt;code&gt;slabclass_t&lt;/code&gt;结构体中，以下几个成员用来维护该class的内存信息：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;slab_list：保存页面的数组，其大小保存在&lt;code&gt;slabs&lt;/code&gt;成员中。&lt;/li&gt;&#xA;&lt;li&gt;sl_curr：可用的&lt;code&gt;item&lt;/code&gt;数量。&lt;/li&gt;&#xA;&lt;li&gt;slots：保存在该&lt;code&gt;slabclass_t&lt;/code&gt;中空闲&lt;code&gt;item&lt;/code&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;slabclass结构体示意图&#34; src=&#34;https://www.codedump.info/media/imgs/20210701-memcached/slabclass-structure.png&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; slabclass结构体示意图 &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;在Memcached的这一套内存管理体系中，一个页面被称为一个&lt;code&gt;slab&lt;/code&gt;，其大小为&lt;code&gt;settings.slab_page_size&lt;/code&gt;；页面中可以分割成多个&lt;code&gt;slot&lt;/code&gt;用来分配内存，一个&lt;code&gt;slot&lt;/code&gt;的大小由该&lt;code&gt;slabclass&lt;/code&gt;的初始大小及&lt;code&gt;factor&lt;/code&gt;来决定，但是需要向上补齐为8位对齐的大小。&lt;/li&gt;&#xA;&lt;li&gt;一个&lt;code&gt;slabclass&lt;/code&gt;中，有预分配好的页面数组，也有被回收的元素组成的空闲slot链表，分配元素时优先从空闲链表中分配（见函数&lt;code&gt;do_slabs_alloc&lt;/code&gt;）。&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;#%e5%86%85%e5%ad%98%e5%88%86%e9%85%8d&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;既然Memcached是一个LRU形式的内存分配器，所以其内存是有限制的，系统中定义了如下几个全局变量来保存当前系统的内存分配信息：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;static size_t mem_limit：内存分配的上限。&lt;/li&gt;&#xA;&lt;li&gt;static size_t mem_malloced：当前分配的内存大小。&lt;/li&gt;&#xA;&lt;li&gt;static void *mem_base：保存内存的起始地址。&lt;/li&gt;&#xA;&lt;li&gt;static void *mem_current：保存内存分配的当前地址。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;在初始化时，系统首先会根据&lt;code&gt;mem_limit&lt;/code&gt;分配一大块内存出来。&lt;/p&gt;</description>
    </item>
    <item>
      <title>boltdb 1.3.0实现分析（四）</title>
      <link>https://www.codedump.info/zh/post/20200726-boltdb-4/</link>
      <pubDate>Sun, 26 Jul 2020 17:55:11 +0800</pubDate>
      <guid>https://www.codedump.info/zh/post/20200726-boltdb-4/</guid>
      <description>&lt;blockquote&gt;&#xA;&lt;p&gt;本文基于boltdb 1.3.0对其实现进行分析。boltdb是etcd系统存储数据使用的KV嵌入式DB，使用Go编码实现，内部是一个B+树结构体。关于etcd、raft协议以及B+树，可以参考之前的文章：&lt;/p&gt;&lt;/blockquote&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.codedump.info/post/20180921-raft/&#34;&gt;Raft算法原理&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.codedump.info/post/20180922-etcd-raft/&#34;&gt;etcd Raft库解析&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.codedump.info/post/20181125-etcd-server/&#34;&gt;Etcd存储的实现&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.codedump.info/post/20200609-btree-1/&#34;&gt;B树、B+树索引算法原理（上）&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+树索引算法原理（下）&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/blockquote&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;本文的写作，主要参考了&lt;a href=&#34;https://www.jianshu.com/p/b86a69892990&#34;&gt;《区块的持久化之BoltDB》系列文章&lt;/a&gt;以及&lt;a href=&#34;https://youjiali1995.github.io/storage/boltdb&#34;&gt;boltdb 源码分析&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;&#xA;&lt;p&gt;前面的章节中，分别讲解了boltdb的页面结构、Bucket结构以及事务相关的逻辑，最后一节讲解boltdb如何实现MVCC。&lt;/p&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;mvcc概述&#34;&gt;&#xA;  MVCC概述&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#mvcc%e6%a6%82%e8%bf%b0&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;数据库的&lt;code&gt;ACID&lt;/code&gt;特性中，&lt;code&gt;Isolation&lt;/code&gt;即隔离性是一个较难实现的特性。&lt;/p&gt;&#xA;&lt;p&gt;一个数据库被修改时，在这次事务提交之前，不希望其他事务操作读到修改的结果。一种常见的办法就是加锁，但是锁的粒度如果很大，就会影响数据库的并发性能，即在写操作完成之前不能进行其他操作。&lt;/p&gt;&#xA;&lt;p&gt;&lt;code&gt;MVCC（Multiversion concurrency control，多版本并发控制）&lt;/code&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;readCommitted&#34; src=&#34;https://www.codedump.info/media/imgs/20200726-boltdb-4/readCommitted.png&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; readCommitted &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;如上图所示，事务A修改了x的值为2，在这个事务提交之前，读事务B读取到的还是修改之前的值1，因为存在有两个该数据的不同版本，并且并没有因为有写操作同时存在而必须等待写操作完成才能进行读操作。在事务A提交之后，才能读到新的值2。（但是这个图里还有另外的问题，即同一个读事务的过程中，前后读到了同一个数据两次不同的值，这叫“不可重复读”，这就是另外一个问题了不在这里展开讨论。）&lt;/p&gt;&#xA;&lt;p&gt;本节讲解boltdb如何实现&lt;code&gt;MVCC&lt;/code&gt;操作，在开始讨论之前先看看boltdb如何管理数据库文件的。&lt;/p&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;映射文件的使用&#34;&gt;&#xA;  映射文件的使用&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e6%98%a0%e5%b0%84%e6%96%87%e4%bb%b6%e7%9a%84%e4%bd%bf%e7%94%a8&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;boltdb通过&lt;code&gt;mmap&lt;/code&gt;系统调用将数据库文件映射到内存中，64位体系下一个进程的虚拟内存空间有128TB，足够映射一个文件了。在把磁盘文件映射到内存之后，对磁盘文件的读写可以直接使用读写内存的操作，由操作系统内核来决定什么时候将哪部分的虚拟内存换入、换出物理内存。&lt;/p&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;db-mmap&#34; src=&#34;https://www.codedump.info/media/imgs/20200726-boltdb-4/db-mmap.png&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; db-mmap &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;对数据库进行内存映射的操作在函数&lt;code&gt;db.mmap&lt;/code&gt;函数中进行，大体流程如下：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-Go&#34; data-lang=&#34;Go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold;font-style:italic;text-decoration:underline&#34;&gt;func&lt;/span&gt; (db *DB) &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;mmap&lt;/span&gt;(minsz &lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;int&lt;/span&gt;) &lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;error&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;db.mmaplock.&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;Lock&lt;/span&gt;()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;defer&lt;/span&gt; db.mmaplock.&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;Unlock&lt;/span&gt;()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#888;font-style:italic&#34;&gt;// 计算至少要多大的文件大小才能满足minsz需求&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#888;font-style:italic&#34;&gt;// Memory-map the data file as a byte slice.&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;if&lt;/span&gt; err := &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;mmap&lt;/span&gt;(db, size); err != &lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;nil&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&#x9;&lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;return&lt;/span&gt; err&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这个函数只有在以下两种情况会被调用到：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;db初始化时，即初次加载db文件到内存映射中。&lt;/li&gt;&#xA;&lt;li&gt;当前文件不够大，需要进行扩容时，即在&lt;code&gt;db.allocate&lt;/code&gt;中分配新页面而当前页面不足需要扩充文件大小时。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;后者也被称为&lt;code&gt;remmap&lt;/code&gt;操作，即以新的大小重新映射文件进行内存中。为了避免每次增加了文件大小都需要重新进行文件内存映射操作，实际上boltdb是对文件大小做了&lt;code&gt;over allocate&lt;/code&gt;操作，具体的计算新文件大小的逻辑在函数&lt;code&gt;DB.mmapSize&lt;/code&gt;函数中实现的，这里不做展开了。&lt;/p&gt;</description>
    </item>
    <item>
      <title>boltdb 1.3.0实现分析（三）</title>
      <link>https://www.codedump.info/zh/post/20200725-boltdb-3/</link>
      <pubDate>Sat, 25 Jul 2020 11:26:33 +0800</pubDate>
      <guid>https://www.codedump.info/zh/post/20200725-boltdb-3/</guid>
      <description>&lt;blockquote&gt;&#xA;&lt;p&gt;本文基于boltdb 1.3.0对其实现进行分析。boltdb是etcd系统存储数据使用的KV嵌入式DB，使用Go编码实现，内部是一个B+树结构体。关于etcd、raft协议以及B+树，可以参考之前的文章：&lt;/p&gt;&lt;/blockquote&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.codedump.info/post/20180921-raft/&#34;&gt;Raft算法原理&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.codedump.info/post/20180922-etcd-raft/&#34;&gt;etcd Raft库解析&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.codedump.info/post/20181125-etcd-server/&#34;&gt;Etcd存储的实现&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.codedump.info/post/20200609-btree-1/&#34;&gt;B树、B+树索引算法原理（上）&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+树索引算法原理（下）&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/blockquote&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;本文的写作，主要参考了&lt;a href=&#34;https://www.jianshu.com/p/b86a69892990&#34;&gt;《区块的持久化之BoltDB》系列文章&lt;/a&gt;以及&lt;a href=&#34;https://youjiali1995.github.io/storage/boltdb&#34;&gt;boltdb 源码分析&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;&#xA;&lt;p&gt;在前面的文章里，分别介绍了boltdb的几种页面格式、Bucket以及Cursor结构，本文介绍boltdb的事务（Transaction）。&lt;/p&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;boltdb支持事务的&lt;code&gt;ACID&lt;/code&gt;特性，使用&lt;code&gt;MVCC&lt;/code&gt;来做并发控制，同时可以执行一个写事务和多个读事务：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;原子性（Atomicity）：未提交的写事务操作都在内存中。在提交写事务的时候，按照B+树数据、freelist、meta元数据的顺序写入文件。在meta元信息写入之前，都可以进行回滚（rollback）操作，只有meta元信息写入成功才能认为写操作执行成功。&lt;/li&gt;&#xA;&lt;li&gt;隔离性（Isolation）：每个读事务开始的时候获得一个版本号，读事务涉及到的页面不会被同时进行的写事务所覆盖；而每次写事务都会更新一个版本号。&lt;/li&gt;&#xA;&lt;li&gt;持久性（Durability）：写事务在提交的时候，会将这次写操作修改的数据（dirty page）分配新的页面，写入文件持久化。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;本节首先讲解boltdb的事务基本实现，下一节讲解boltdb事务如何实现&lt;code&gt;MVCC&lt;/code&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;#%e4%ba%8b%e5%8a%a1%e5%88%9d%e5%a7%8b%e5%8c%96&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;boltdb中，任何一次读写操作，都有一个事务与之对应。这时候首先会调用&lt;code&gt;DB.Begin&lt;/code&gt;函数返回一个事务，而传入的参数会根据情况分别创建写和读事务：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-Go&#34; data-lang=&#34;Go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold;font-style:italic;text-decoration:underline&#34;&gt;func&lt;/span&gt; (db *DB) &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;Begin&lt;/span&gt;(writable &lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;bool&lt;/span&gt;) (*Tx, &lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;error&lt;/span&gt;) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;if&lt;/span&gt; writable {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&#x9;&lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;return&lt;/span&gt; db.&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;beginRWTx&lt;/span&gt;()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;return&lt;/span&gt; db.&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;beginTx&lt;/span&gt;()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;可以看到，根据是否是写事务，会分别调用&lt;code&gt;beginRWTx&lt;/code&gt;和&lt;code&gt;beginTx&lt;/code&gt;来创建读写事务和只读事务。&lt;/p&gt;&#xA;&lt;p&gt;&lt;code&gt;DB&lt;/code&gt;结构体中，仅有一个写事务成员，而读事务则可以同时存在多个，因此同一个时间只能有一个写事务：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-Go&#34; data-lang=&#34;Go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold;font-style:italic;text-decoration:underline&#34;&gt;type&lt;/span&gt; DB &lt;span style=&#34;font-weight:bold;font-style:italic;text-decoration:underline&#34;&gt;struct&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#888;font-style:italic&#34;&gt;// ...&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;rwtx     *Tx&#x9;&#x9;&#x9;&#x9;&lt;span style=&#34;color:#888;font-style:italic&#34;&gt;// 同一时间只能有一个未完成的写事务&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;txs      []*Tx&#x9;&#x9;&#x9;&lt;span style=&#34;color:#888;font-style:italic&#34;&gt;// 保存未完成的读事务的，读事务可以有多个，写事务一个时间只能有一个，就在rwtx里面&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}  &#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&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;beginTx&#34; src=&#34;https://www.codedump.info/media/imgs/20200725-boltdb-3/beginTx.png&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; beginTx &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;了解了在&lt;code&gt;DB&lt;/code&gt;中如何使用事务，下面来看事务结构体的定义：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-Go&#34; data-lang=&#34;Go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold;font-style:italic;text-decoration:underline&#34;&gt;type&lt;/span&gt; Tx &lt;span style=&#34;font-weight:bold;font-style:italic;text-decoration:underline&#34;&gt;struct&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;writable       &lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;bool&lt;/span&gt;&#x9;&lt;span style=&#34;color:#888;font-style:italic&#34;&gt;// 是否写事务&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;managed        &lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;bool&lt;/span&gt;&#x9;&lt;span style=&#34;color:#888;font-style:italic&#34;&gt;//&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;db             *DB&#x9;&lt;span style=&#34;color:#888;font-style:italic&#34;&gt;// 对应的db&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;meta           *meta&#x9;&lt;span style=&#34;color:#888;font-style:italic&#34;&gt;// 对应的meta数据指针&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;root           Bucket&#x9;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;pages          &lt;span style=&#34;font-weight:bold;font-style:italic;text-decoration:underline&#34;&gt;map&lt;/span&gt;[pgid]*page&#x9;&lt;span style=&#34;color:#888;font-style:italic&#34;&gt;// 涉及到的page&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;stats          TxStats&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;commitHandlers []&lt;span style=&#34;font-weight:bold;font-style:italic;text-decoration:underline&#34;&gt;func&lt;/span&gt;()&#x9;&lt;span style=&#34;color:#888;font-style:italic&#34;&gt;// commit回调函数数组&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;WriteFlag &lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;int&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;成员释义如下：&lt;/p&gt;</description>
    </item>
    <item>
      <title>boltdb 1.3.0实现分析（二）</title>
      <link>https://www.codedump.info/zh/post/20200711-boltdb-2/</link>
      <pubDate>Sat, 11 Jul 2020 09:33:06 +0800</pubDate>
      <guid>https://www.codedump.info/zh/post/20200711-boltdb-2/</guid>
      <description>&lt;blockquote&gt;&#xA;&lt;p&gt;本文基于boltdb 1.3.0对其实现进行分析。boltdb是etcd系统存储数据使用的KV嵌入式DB，使用Go编码实现，内部是一个B+树结构体。关于etcd、raft协议以及B+树，可以参考之前的文章：&lt;/p&gt;&lt;/blockquote&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.codedump.info/post/20180921-raft/&#34;&gt;Raft算法原理&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.codedump.info/post/20180922-etcd-raft/&#34;&gt;etcd Raft库解析&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.codedump.info/post/20181125-etcd-server/&#34;&gt;Etcd存储的实现&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.codedump.info/post/20200609-btree-1/&#34;&gt;B树、B+树索引算法原理（上）&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+树索引算法原理（下）&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/blockquote&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;本文的写作，主要参考了&lt;a href=&#34;https://www.jianshu.com/p/b86a69892990&#34;&gt;《区块的持久化之BoltDB》系列文章&lt;/a&gt;以及&lt;a href=&#34;https://youjiali1995.github.io/storage/boltdb&#34;&gt;boltdb 源码分析&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;&#xA;&lt;p&gt;在&lt;a href=&#34;https://www.codedump.info/post/20200625-boltdb-1/&#34;&gt;上一节&lt;/a&gt;里面，系统的介绍了Boltdb中几种类型页面的格式，有了这些基础，本节开始介绍boltdb中的Bucket结构。&lt;/p&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;bucket&#34;&gt;&#xA;  Bucket&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#bucket&#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%bf%b0&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;在上一节中，Bucket类比于mysql中的table，在boltdb中，&lt;code&gt;meta&lt;/code&gt;页面中有一个成员&lt;code&gt;bucket&lt;/code&gt;，其存储了整个数据库根bucket的信息，而一个数据库中存储的其他table的信息，则作为子bucket存储到Bucket中。这几个数据结构的关系如下：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-Go&#34; data-lang=&#34;Go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold;font-style:italic;text-decoration:underline&#34;&gt;type&lt;/span&gt; DB &lt;span style=&#34;font-weight:bold;font-style:italic;text-decoration:underline&#34;&gt;struct&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#888;font-style:italic&#34;&gt;// ...&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;meta0    *meta&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;meta1    *meta  &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold;font-style:italic;text-decoration:underline&#34;&gt;type&lt;/span&gt; meta &lt;span style=&#34;font-weight:bold;font-style:italic;text-decoration:underline&#34;&gt;struct&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#888;font-style:italic&#34;&gt;// ...&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;root     bucket&#x9;&lt;span style=&#34;color:#888;font-style:italic&#34;&gt;// 根bucket的信息&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold;font-style:italic;text-decoration:underline&#34;&gt;type&lt;/span&gt; Bucket &lt;span style=&#34;font-weight:bold;font-style:italic;text-decoration:underline&#34;&gt;struct&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;*bucket&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#888;font-style:italic&#34;&gt;// ...&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  buckets  &lt;span style=&#34;font-weight:bold;font-style:italic;text-decoration:underline&#34;&gt;map&lt;/span&gt;[&lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;string&lt;/span&gt;]*Bucket &lt;span style=&#34;color:#888;font-style:italic&#34;&gt;// 存储子bucket的对应关系&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold;font-style:italic;text-decoration:underline&#34;&gt;type&lt;/span&gt; bucket &lt;span style=&#34;font-weight:bold;font-style:italic;text-decoration:underline&#34;&gt;struct&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#888;font-style:italic&#34;&gt;// 根节点的page id&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;root pgid &lt;span style=&#34;color:#888;font-style:italic&#34;&gt;// page id of the bucket&amp;#39;s root-level page&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#888;font-style:italic&#34;&gt;// 单调递增的序列号&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;sequence &lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;uint64&lt;/span&gt; &lt;span style=&#34;color:#888;font-style:italic&#34;&gt;// monotonically incrementing, used by NextSequence()&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;在&lt;code&gt;bucket&lt;/code&gt;数据结构中，两个成员的作用是：&lt;/p&gt;</description>
    </item>
    <item>
      <title>boltdb 1.3.0实现分析（一）</title>
      <link>https://www.codedump.info/zh/post/20200625-boltdb-1/</link>
      <pubDate>Thu, 25 Jun 2020 21:43:17 +0800</pubDate>
      <guid>https://www.codedump.info/zh/post/20200625-boltdb-1/</guid>
      <description>&lt;blockquote&gt;&#xA;&lt;p&gt;本文基于boltdb 1.3.0对其实现进行分析。boltdb是etcd系统存储数据使用的KV嵌入式DB，使用Go编码实现，内部是一个B+树结构体。关于etcd、raft协议以及B+树，可以参考之前的文章：&lt;/p&gt;&lt;/blockquote&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.codedump.info/post/20180921-raft/&#34;&gt;Raft算法原理&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.codedump.info/post/20180922-etcd-raft/&#34;&gt;etcd Raft库解析&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.codedump.info/post/20181125-etcd-server/&#34;&gt;Etcd存储的实现&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.codedump.info/post/20200609-btree-1/&#34;&gt;B树、B+树索引算法原理（上）&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+树索引算法原理（下）&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/blockquote&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;本文的写作，主要参考了&lt;a href=&#34;https://www.jianshu.com/p/b86a69892990&#34;&gt;《区块的持久化之BoltDB》系列文章&lt;/a&gt;以及&lt;a href=&#34;https://youjiali1995.github.io/storage/boltdb&#34;&gt;boltdb 源码分析&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&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;boltdb是etcd项目使用的kv存储引擎，代码量不大，不算测试用例的话仅有几千行代码量，是入门存储引擎不错的参考项目。&lt;/p&gt;&#xA;&lt;p&gt;boltdb中与mysql这类的关系数据库相对应的概念列举如下：&lt;/p&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;boltdb&lt;/th&gt;&#xA;          &lt;th&gt;mysql&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;db&lt;/td&gt;&#xA;          &lt;td&gt;database&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;bucket&lt;/td&gt;&#xA;          &lt;td&gt;table&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;即：在boltdb中，db代表一个数据库，对应一个db文件；而一个数据库中可能有多个表，对应的概念就是boltdb中的bucket。&lt;/p&gt;&#xA;&lt;p&gt;另外，对B+树有了解的都知道，B+树中为了减少磁盘读写次数，每次读写都是以页为单位的，对应到boltdb中用&lt;code&gt;page&lt;/code&gt;数据结构表示，&lt;code&gt;page&lt;/code&gt;只是描述磁盘上一个页面的数据结构，当一个页面读取到内存中时，就使用&lt;code&gt;node&lt;/code&gt;结构体来描述。另外，既然落地到磁盘的单位是页，就需要有数据结构来管理页面的分配，这部分使用&lt;code&gt;freelist&lt;/code&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;page-struct&#34; src=&#34;https://www.codedump.info/media/imgs/20200625-boltdb-1/page-struct.png&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; page-struct &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;#%e6%95%b0%e6%8d%ae%e5%ba%93%e6%96%87%e4%bb%b6%e7%9a%84%e7%a3%81%e7%9b%98%e5%b8%83%e5%b1%80%e5%92%8c%e9%a1%b5%e9%9d%a2&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;前面提到过，boltdb中以页面为单位来进行磁盘的读写操作，一个页面的大小一般而言与操作系统的页面一致，即4K大小。在boltdb中，分为以下几种类型的页面：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;存储meta元数据的页面。&lt;/li&gt;&#xA;&lt;li&gt;存储freelist，即管理页面数据的页面。&lt;/li&gt;&#xA;&lt;li&gt;Branch页面，存储B+树索引节点，也就是内部节点的页面。&lt;/li&gt;&#xA;&lt;li&gt;Leaf页面，存储B+树数据节点，也就是叶子节点的页面。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;boltdb代码中定义页面类型如下：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-Go&#34; data-lang=&#34;Go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold;font-style:italic;text-decoration:underline&#34;&gt;const&lt;/span&gt; (&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;branchPageFlag   = 0x01&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;leafPageFlag     = 0x02&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;metaPageFlag     = 0x04&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;freelistPageFlag = 0x10&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这四种页面，在boltdb的数据库文件的布局大体如下：&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;boltdb-layout&#34; src=&#34;https://www.codedump.info/media/imgs/20200625-boltdb-1/boltdb-layout.png&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; boltdb-layout &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;最开始的两个页面是两个meta页面，至于为什么是两个，后面再展开讨论。&lt;/li&gt;&#xA;&lt;li&gt;紧跟着的一个页面是freelist页面。&lt;/li&gt;&#xA;&lt;li&gt;从上面可知，数据库文件中最开始的三个页面存的都是管理信息，此后数据数据型的branch以及leaf页面了。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;接下来就这几种页面具体的结构展开说明，不过在此之前还是首先来看看&lt;code&gt;page&lt;/code&gt;结构体，它用于表示一个磁盘页面的数据结构。&lt;/p&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;page结构体&#34;&gt;&#xA;  page结构体&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#page%e7%bb%93%e6%9e%84%e4%bd%93&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;&lt;code&gt;page&lt;/code&gt;结构体的定义如下：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-Go&#34; data-lang=&#34;Go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold;font-style:italic;text-decoration:underline&#34;&gt;type&lt;/span&gt; pgid &lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;uint64&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold;font-style:italic;text-decoration:underline&#34;&gt;type&lt;/span&gt; page &lt;span style=&#34;font-weight:bold;font-style:italic;text-decoration:underline&#34;&gt;struct&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    id       pgid&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    flags    &lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;uint16&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    count    &lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;uint16&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    overflow &lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;uint32&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ptr      &lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;uintptr&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;其中：&lt;/p&gt;</description>
    </item>
    <item>
      <title>B树、B&#43;树索引算法原理（下）</title>
      <link>https://www.codedump.info/zh/post/20200615-btree-2/</link>
      <pubDate>Mon, 15 Jun 2020 22:44:21 +0800</pubDate>
      <guid>https://www.codedump.info/zh/post/20200615-btree-2/</guid>
      <description>&lt;blockquote&gt;&#xA;&lt;p&gt;这一段时间由于在阅读boltdb代码的缘故，找机会学习了B树及B+树的算法原理，这个系列会花两个篇幅分别介绍这两种数据结构的实现，其用于数据库索引中的基本原理。&lt;/p&gt;&lt;/blockquote&gt;&#xA;&lt;p&gt;在&lt;a href=&#34;https://www.codedump.info/post/20200609-btree-1/&#34;&gt;上一篇文章&lt;/a&gt;中，介绍了数据库索引的简单概念，以及B树的结构及核心算法，这一篇将继续介绍B树的变形B+树。&lt;/p&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;b树的定义及性质&#34;&gt;&#xA;  B+树的定义及性质&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#b%e6%a0%91%e7%9a%84%e5%ae%9a%e4%b9%89%e5%8f%8a%e6%80%a7%e8%b4%a8&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;B+树之于B树，最大的不同在于：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;B树的数据可以存储在内部节点上，也可以存储在叶子节点上。&lt;/li&gt;&#xA;&lt;li&gt;而在B+树中，内部节点上仅存放数据的索引，数据只存储在叶子节点上。在内部节点中的键值，被称为“索引”，由于是数据索引，因此可能出现同一个键值，既出现在内部节点，也出现在叶子节点中的情况。&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;/ul&gt;&#xA;&lt;p&gt;同时，B+树为了方便范围查询，叶子节点之间还用指针串联起来。&lt;/p&gt;&#xA;&lt;p&gt;以下是一颗B+树的典型结构：&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&amp;#43;tree&#34; src=&#34;https://www.codedump.info/media/imgs/20200615-btree-2/b&amp;#43;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+树对比B树有以下优点：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;索引节点上由于只有索引而没有数据，所以索引节点上能存储比B树更多的索引，这样树的高度就会更矮。按照我们上一篇中介绍数据库索引的内容，这种面向磁盘的数据结构，树的高度越矮，磁盘寻道的次数就会越少。&lt;/li&gt;&#xA;&lt;li&gt;因为数据都集中在叶子节点了，而所有叶子节点的高度相同，那么可以在叶子节点中增加前后指针，指向同一个父节点的相邻兄弟节点，给范围查询提供遍历。比如这样的SQL语句：&lt;code&gt;select * from tbl where t &amp;gt; 10&lt;/code&gt;，如果使用B+树存储数据的话，可以首先定位到数据为10的节点，再沿着它的next指针一路找到所有在该叶子节点右边的叶子节点数据返回。而如果使用B树结构，由于数据既可以存储在内部节点也可以存储在叶子节点，范围查询可想而知是很繁琐的。&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%a0%b8%e5%bf%83%e7%ae%97%e6%b3%95&#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%8f%92%e5%85%a5%e7%ae%97%e6%b3%95&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;B+树的插入算法与B树的很相近，都是：&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;比如在下图的B+树中，向这里插入新的数据&lt;code&gt;10&lt;/code&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;slide01b&#34; src=&#34;https://www.codedump.info/media/imgs/20200615-btree-2/slide01b.gif&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; slide01b &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;由于插入节点&lt;code&gt;[7,11]&lt;/code&gt;在插入之后并没有溢出，所以可以直接变成&lt;code&gt;[7,10,11]&lt;/code&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;slide01c&#34; src=&#34;https://www.codedump.info/media/imgs/20200615-btree-2/slide01c.gif&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; slide01c &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;而如下图的B+树中，插入数据&lt;code&gt;4&lt;/code&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;slide02b&#34; src=&#34;https://www.codedump.info/media/imgs/20200615-btree-2/slide02b.gif&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; slide02b &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;由于所在节点&lt;code&gt;[2,3,5]&lt;/code&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;slide02g&#34; src=&#34;https://www.codedump.info/media/imgs/20200615-btree-2/slide02g.gif&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; slide02g &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;&lt;code&gt;[2,3,4,5]&lt;/code&gt;分裂成了&lt;code&gt;[2,3]&lt;/code&gt;和&lt;code&gt;[4,5]&lt;/code&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;综上，需要在父节点中新增索引&lt;code&gt;4&lt;/code&gt;和两个指向新节点的指针。&lt;/p&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;删除算法&#34;&gt;&#xA;  删除算法&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e5%88%a0%e9%99%a4%e7%ae%97%e6%b3%95&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;B+树的删除算法，与B树类似，分为以下几步：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;首先查询到键值所在的叶子节点，删除该叶子节点的数据。&lt;/li&gt;&#xA;&lt;li&gt;如果删除叶子节点之后的数据数量，满足B+树的平衡条件，则直接返回不用往下走了。&lt;/li&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;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;在上面平衡操作中，如果是进行了合并操作，就需要向上修正父节点的指针：删除被合并节点的键值以及指针。由于做了删除操作，可能父节点也会不平衡，那么就按照前面的步骤也对父节点进行重新平衡操作，这样一直到某个节点平衡为止。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;下面结合&lt;a href=&#34;http://www.mathcs.emory.edu/~cheung/Courses/554/Syllabus/3-index/B-tree=delete1.html&#34;&gt;B-tree=delete1&lt;/a&gt;、&lt;a href=&#34;http://www.mathcs.emory.edu/~cheung/Courses/554/Syllabus/3-index/B-tree=delete2.html&#34;&gt;B-tree=delete2&lt;/a&gt; 的图示对删除算法展开具体的分析。&lt;/p&gt;&#xA;&lt;h3 class=&#34;heading&#34; id=&#34;从叶子节点中删除数据&#34;&gt;&#xA;  从叶子节点中删除数据&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e4%bb%8e%e5%8f%b6%e5%ad%90%e8%8a%82%e7%82%b9%e4%b8%ad%e5%88%a0%e9%99%a4%e6%95%b0%e6%8d%ae&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h3&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;h4 class=&#34;heading&#34; id=&#34;借用兄弟节点数据进行重平衡操作&#34;&gt;&#xA;  借用兄弟节点数据进行重平衡操作&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e5%80%9f%e7%94%a8%e5%85%84%e5%bc%9f%e8%8a%82%e7%82%b9%e6%95%b0%e6%8d%ae%e8%bf%9b%e8%a1%8c%e9%87%8d%e5%b9%b3%e8%a1%a1%e6%93%8d%e4%bd%9c&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h4&gt;&#xA;&lt;p&gt;在下图中，从叶子节点中删除数据之后，只剩下数据&lt;code&gt;[11]&lt;/code&gt;：&lt;/p&gt;</description>
    </item>
    <item>
      <title>B树、B&#43;树索引算法原理（上）</title>
      <link>https://www.codedump.info/zh/post/20200609-btree-1/</link>
      <pubDate>Tue, 09 Jun 2020 18:40:46 +0800</pubDate>
      <guid>https://www.codedump.info/zh/post/20200609-btree-1/</guid>
      <description>&lt;blockquote&gt;&#xA;&lt;p&gt;这一段时间由于在阅读boltdb代码的缘故，找机会学习了B树及B+树的算法原理，这个系列会花两个篇幅分别介绍这两种数据结构的实现，其用于数据库索引中的基本原理。&lt;/p&gt;&lt;/blockquote&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;b树数据库索引原理&#34;&gt;&#xA;  B树数据库索引原理&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#b%e6%a0%91%e6%95%b0%e6%8d%ae%e5%ba%93%e7%b4%a2%e5%bc%95%e5%8e%9f%e7%90%86&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;在一堆数据中查找一个数据时，常用的数据结构有二叉查找树（binary search tree，简称BST）、哈希桶等。以BST为例，常见的实现有AVT、红黑树等，由于这类型的树是平衡的，每次比较操作都会去掉当前数据量一半的数据，因此查找的时间复杂度为&lt;code&gt;O(log2n)&lt;/code&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;bst-example&#34; src=&#34;https://www.codedump.info/media/imgs/20200609-btree-1/bst-example.png&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; bst-example &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;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;因为读写磁盘的这个特点，因此对应的数据结构应该尽量的满足“局部性原理”：“当一个数据被用到时，其附近的数据也通常会马上被使用”，为了满足局部性原理，应该：&lt;strong&gt;将逻辑上相邻的数据在物理上也尽量存储在一起&lt;/strong&gt;。这样才能减少读写磁盘的数量。&lt;/p&gt;&#xA;&lt;p&gt;所以，对比起一个节点只能存储一个数据的BST类数据结构来，要求这种数据结构在形状上更“胖”、更加“扁平”，即：每个节点能容纳更多的数据，这样就能降低树的高度，同时让逻辑上相邻的数据都能尽量的存储在物理上也相邻的硬盘空间上，减少磁盘读写。&lt;/p&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;disk-ds&#34; src=&#34;https://www.codedump.info/media/imgs/20200609-btree-1/disk-ds.png&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; disk-ds &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;图中从根节点出发，查找数据14的过程中，经过的第二个节点中有键值&lt;code&gt;[3,7,13]&lt;/code&gt;，这三个值在“逻辑”上是相邻的，如果它们在磁盘上的存储也能做到在“物理”上相邻，那么只需要一次读操作就能把这个节点的数据从磁盘上加载到内存中进行数据比较，这样整个查找过程就只需要两次磁盘读操作。&lt;/p&gt;&#xA;&lt;p&gt;在这里，一个节点越“胖”，意味着扇出（fanout）越大，同时高度越低，这两个性质决定了：&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;可以证明，查找数据的次数（searchnum）与degree、以及数据总量有以下关系：&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;btree-num&#34; src=&#34;https://www.codedump.info/media/imgs/20200609-btree-1/btree-num.png&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; btree-num &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;B树和B+树就是两种利用磁盘局部性原理进行优化的树结构，B+树基于B树做了一些改进，这里首先将介绍B树的原理。本系列将用两篇文章讲解这两种数据结构的原理，并且提供Python实现代码。&lt;/p&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;b树的定义及性质&#34;&gt;&#xA;  B树的定义及性质&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#b%e6%a0%91%e7%9a%84%e5%ae%9a%e4%b9%89%e5%8f%8a%e6%80%a7%e8%b4%a8&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;在B树中，分为两种节点：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;内部节点（internal node）：存储了数据以及指向其子节点的指针。&lt;/li&gt;&#xA;&lt;li&gt;叶子节点（leaf node）：与内部节点不同的是，叶子节点只存储数据，并没有子节点。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;一个数据，既可能存在内部节点上，也可能存在叶子节点上，这一点是与后面讲到的B+树最大的不同，后者只会将数据存储在叶子节点上。&lt;/p&gt;&#xA;&lt;p&gt;创建B树时，需要输入一个degree参数（以下简写为t），该参数决定了每个节点上数据量的多少，即节点的“胖”、“瘦”程度，而节点的胖瘦程度又会影响整棵树的高度，因为越胖的节点树高度就会越矮。&lt;/p&gt;&#xA;&lt;p&gt;为了维持B树的平衡性，需要满足以下的属性：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;在每个节点上的键值，以递增顺序排列，即&lt;code&gt;node.keys[i] &amp;lt;= node.keys[i+1]&lt;/code&gt;。&lt;/li&gt;&#xA;&lt;li&gt;在一个键值左边的子树，其键值大于该键值右边子树的所有键值，即&lt;code&gt;node.keys[i] &amp;gt; max(node.child[i]的所有键值)&lt;/code&gt;；同时，在一个键值右边的子树，其键值的最小值都不小于该键值，即&lt;code&gt;node.keys[i] &amp;lt;= min(node.child[i + 1]的所有键值)&lt;/code&gt;。具体情况可以在下面的图中进行说明。&lt;/li&gt;&#xA;&lt;li&gt;在内部节点中，指向子节点的指针数量总是存储数据节点的数量+1，即：&lt;code&gt;num(node.child) = num(node.keys) + 1&lt;/code&gt;。&lt;/li&gt;&#xA;&lt;li&gt;所有叶子节点的高度一致。&lt;/li&gt;&#xA;&lt;li&gt;无论是内部节点还是叶子节点，其存储的键值数量在&lt;code&gt;[t-1,2t-1]&lt;/code&gt;之间，如果数量不满足此条件，需要做重平衡操作。如果少于&lt;code&gt;t-1&lt;/code&gt;，需要借用或合并数据；反之，如果数据量大于&lt;code&gt;2t-1&lt;/code&gt;，则需要分裂成两个节点。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;我们来看下面的图示，该图中的B树，t参数的值为2（&lt;strong&gt;需要特别说明的是，一棵树中每个存储数据的地方，应该既有键值（key）也有数据（value），本文中为了简单起见，存储的数据只有键值。&lt;/strong&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;btree-example&#34; src=&#34;https://www.codedump.info/media/imgs/20200609-btree-1/btree-example.png&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; btree-example &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;t=2&lt;/code&gt;，所有所有节点的键值数量在&lt;code&gt;[1,3]&lt;/code&gt;之间。&lt;/li&gt;&#xA;&lt;li&gt;所有叶子节点的高度相同。&lt;/li&gt;&#xA;&lt;li&gt;以左边的内部节点为例，其第一个键值为3，即该节点的&lt;code&gt;keys[0]=3&lt;/code&gt;，而该键值的左边子树的键值为&lt;code&gt;[1,2]&lt;/code&gt;，都小于3，即&lt;code&gt;keys[0]&amp;gt;max(child[0]的所有键值)&lt;/code&gt;；而其右边子树的键值为&lt;code&gt;[4,5,6]&lt;/code&gt;，都不小于3，即&lt;code&gt;keys[0]&amp;lt;=min(child[1]的所有键值)&lt;/code&gt;。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;b树算法原理&#34;&gt;&#xA;  B树算法原理&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#b%e6%a0%91%e7%ae%97%e6%b3%95%e5%8e%9f%e7%90%86&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;了解了B树的性质，下面讨论B树中的两个核心操作：插入及删除。这两个操作的核心，都是在操作如果破坏了B树的平衡性之后，进行重新平衡以满足B树的性质。&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%8f%92%e5%85%a5%e6%95%b0%e6%8d%ae&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;向B树中插入一个数据，可能会导致节点的数据变满，即不满足上面提到的节点数据数量在&lt;code&gt;[t,2t-1]&lt;/code&gt;这个性质。此时需要对节点进行分裂节点操作：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;将数据变满（即节点数据量为&lt;code&gt;2t&lt;/code&gt;）的节点，分为左右两个数据量分别为&lt;code&gt;t-1&lt;/code&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;</description>
    </item>
    <item>
      <title>Leveldb代码阅读笔记</title>
      <link>https://www.codedump.info/zh/post/20190215-leveldb/</link>
      <pubDate>Fri, 15 Feb 2019 08:52:47 +0800</pubDate>
      <guid>https://www.codedump.info/zh/post/20190215-leveldb/</guid>
      <description>&lt;p&gt;本文基于leveldb 1.9.0代码。&lt;/p&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;整体架构&#34;&gt;&#xA;  整体架构&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e6%95%b4%e4%bd%93%e6%9e%b6%e6%9e%84&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&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;leveldb&#34; src=&#34;https://www.codedump.info/media/imgs/20190215-leveldb/leveldb.png&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; leveldb &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;如上图，leveldb的数据存储在内存以及磁盘上，其中：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;memtable：存储在内存中的数据，使用skiplist实现。&lt;/li&gt;&#xA;&lt;li&gt;immutable memtable：与memtable一样，只不过这个memtable不能再进行修改，会将其中的数据落盘到level 0的sstable中。&lt;/li&gt;&#xA;&lt;li&gt;多层sstable：leveldb使用多个层次来存储sstable文件，这些文件分布在磁盘上，这些文件都是根据键值有序排列的，其中0级的sstable的键值可能会重叠，而level 1及以上的sstable文件不会重叠。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;在上面这个存储层次中，越靠上的数据越新，即同一个键值如果同时存在于memtable和immutable memtable中，则以memtable中的为准。&lt;/p&gt;&#xA;&lt;p&gt;另外，图中还使用箭头来表示了合并数据的走向，即：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-C&#34; data-lang=&#34;C&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;memtable -&amp;gt; immutable memtable -&amp;gt; level 0 sstable -&amp;gt; level 1 sstable -&amp;gt; ... -&amp;gt; level N sstable&lt;span style=&#34;&#34;&gt;。&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;以下将针对这几部分展开讨论。&lt;/p&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;log文件&#34;&gt;&#xA;  Log文件&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#log%e6%96%87%e4%bb%b6&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;写入数据的时候，最开始会写入到log文件中，由于是顺序写入文件，所以写入速度很快，可以马上返回。&lt;/p&gt;&#xA;&lt;p&gt;来看Log文件的结构：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;一个Log文件由多个Block组成，每个Block大小为32KB。&lt;/li&gt;&#xA;&lt;li&gt;一个Block内部又有多个Record组成，Record分为四种类型：&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Full：一个Record占满了整个Block存储空间。&lt;/li&gt;&#xA;&lt;li&gt;First：一个Block的第一个Record。&lt;/li&gt;&#xA;&lt;li&gt;Last：一个Block的最后一个Record。&lt;/li&gt;&#xA;&lt;li&gt;Middle：其余的都是Middle类型的Record。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;Record的结构如下：&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Header部分&#xA;&lt;ul&gt;&#xA;&lt;li&gt;32位长度的CRC Checksum：存储这个Record的数据校验值，用于检测Record合法性。&lt;/li&gt;&#xA;&lt;li&gt;16位长度的Length：存储数据部分长度。&lt;/li&gt;&#xA;&lt;li&gt;8位长度的Type：存储Record类型，就是上面说的四种类型。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;数据部分&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&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;log-file&#34; src=&#34;https://www.codedump.info/media/imgs/20190215-leveldb/log-file.png&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; log-file &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;memtable&#34;&gt;&#xA;  memtable&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#memtable&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;memtable用于存储在内存中还未落盘到sstable中的数据，这部分使用跳表（skiplist）做为底层的数据结构，这里先简单描述一下跳表的工作原理。&lt;/p&gt;&#xA;&lt;p&gt;如果数据存放在一个普通的有序链表中，那么查找数据的时间复杂度就是O(n)。跳表的设计思想在于：链表中的每个元素，都有多个层次，查找某一个元素时，遍历该链表的时候，根据层次来跳过（skip）中间某些明显不满足需求的元素，以达到加快查找速度的目的，如下图所示：&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;skiplist&#34; src=&#34;https://www.codedump.info/media/imgs/20190215-leveldb/skiplist.png&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; skiplist &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;在以上这个跳表中，查找元素6的流程，大体如下：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;构建一个每个链表元素最多有5个元素的跳表。&lt;/li&gt;&#xA;&lt;li&gt;由于6大于链表的第一个元素1，因此如果存在必然在1之后的元素中，因此进入元素1的指针数组中，从上往下查找元素4：&#xA;&lt;ul&gt;&#xA;&lt;li&gt;第一层：指向的指针为Nil空指针，不满足需求，继续往下查找；&lt;/li&gt;&#xA;&lt;li&gt;第二层：指向的指针保存的数据为4，小于待查找的元素4，因此如果元素6存在也必然在4之后，因此指针跳转到元素4所在的位置，继续从上往下开始查找。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;到了元素4所在的指针数组，开始从上往下继续查找：&#xA;&lt;ul&gt;&#xA;&lt;li&gt;第一层：指向的指针保存的数据为6，查找完毕。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&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;如果一个元素出现在第i层的链表中，那么也必然会在第i层以下的链表中出现。&lt;/li&gt;&#xA;&lt;li&gt;链表的每个节点中，垂直方向的数组存储的数据都是一样的，水平方向的指针指向链表的下一个元素。&lt;/li&gt;&#xA;&lt;li&gt;最底层的链表包含所有元素，也就是说，在最底层数据结构退化为一个普通的有序链表。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;sstable文件&#34;&gt;&#xA;  sstable文件&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#sstable%e6%96%87%e4%bb%b6&#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;#%e5%a4%a7%e4%bd%93%e7%bb%93%e6%9e%84&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;首先来看sstable文件的整体结构，如下图：&lt;/p&gt;</description>
    </item>
  </channel>
</rss>
