B+ ツリーの導入、使用法、および C++ コード実装について

データ構造とアルゴリズムの重要性は自明であり、いくつかの優れたオープンソース プロジェクトの中核はデータ構造とアルゴリズムです。実際のプログラミングでは、さまざまなフレームワークやアルゴリズムで B ツリーと B+ ツリーがよく見られます。特にデータベースのデータベースエンジンにおいては重要な位置を占めています。次に、単純な二分木から木の進化まで、マルチフォークツリー(B-tree、B+-tree)の起源、機能、動作、実用化について詳しく説明します。

B+ ツリーの紹介

B+ ツリーは、データベース システムやファイル システムで一般的に使用されるデータ構造の一種で、その主な目的は、大量のデータをディスク上に保存し、インデックスを付けて検索効率を向上させることです。B+ ツリーと B ツリーは非常に似ていますが、重要な違いがあります。B+ ツリーはデータをリーフ ノードにのみ保存し、非リーフ ノードはインデックス情報のみを保存します。ディスク上のレコードの読み取りコストは非常に高いため、この構造により、B+ ツリーはディスク読み取り方法によりよく適応します。B+ ツリーのリーフ ノードはリンク リストを形成し、範囲クエリを簡単に実装できます。

これらは、大規模なデータセット、特にディスクに保存されているデータセットの効率的な検索とインデックス作成を可能にするように設計されています。B+ ツリーでは、データはツリー構造に編成されたノードに格納されます。各ノードには、一連のキーと、子ノードまたはデータ レコードへのポインターが含まれています。ツリーのルート ノードには子ノードへのポインタが含まれ、リーフ ノードにはデータ レコードへのポインタが含まれます。

B+ツリーの利点

B+ ツリーの主な利点の 1 つは、効率的な範囲クエリが可能になることであり、これはデータベース アプリケーションで特に役立ちます。これは、各ノードのキーがソートされているため、ツリーを簡単に走査して目的のデータを見つけることができるためです。

B+ ツリーは、MySQL、PostgreSQL、SQLite などのオープンソース データベース プロジェクトで広く使用されています。このうち、MySQL の InnoDB ストレージ エンジンはインデックス構造として B+ ツリーを使用します。さらに、ファイル システムとキーバリュー ストレージ システムは、コア データ構造として B+ ツリーを使用することがよくあります。

NTFS、ReiserFS、NSS、XFS、JFS、ReFS、BFS などのファイル システムはすべて、B+ ツリーをメタデータ インデックスとして使用します。B+ ツリーの特徴は、データを安定して秩序正しく保つことができ、その挿入と変更の対数時間計算量が比較的安定していることです。B+ ツリー要素はボトムアップで挿入されます。

B+ ツリーの視覚化

ツリー形状 (4 次 B+ ツリー) を次の図に示します。

ここに画像の説明を挿入

B+ツリーの由来

B+ ツリーは、二分探索ツリー、バランス二分木、B ツリーの 3 つのデータ構造から発展しました。バイナリ ツリーから始めて、B+ ツリーの進化を見てみましょう。 

二分探索木

まず、写真を見てみましょう。

編集

図からわかるように、ユーザー テーブル (ユーザー情報テーブル) に対して二分探索木インデックスを確立しました。

図中の丸が二分探索木のノードであり、そのノードにはキー(key)とデータ(data)が格納されます。キーはユーザーテーブルのidに対応し、データはユーザーテーブルの行データに対応します。

二分探索木の特徴は、任意のノードの左の子ノードのキー値が現在のノードのキー値より小さく、右の子ノードのキー値が現在のノードのキー値より大きいことです。 。最上位のノードはルート ノードと呼ばれ、子のないノードはリーフ ノードと呼ばれます。

作成したバイナリ検索ツリー インデックスを使用して、id=12 のユーザー情報を検索する必要がある場合、検索プロセスは次のようになります。

  • ルート ノードを現在のノードとして取得し、12 を現在のノードのキー値 10 と比較します。12 は 10 より大きいため、現在のノード > 右側の子ノードを現在のノードとして取得します。
  • 引き続き 12 と現在のノードのキー値 13 を比較し、12 が 13 より小さいことを確認し、現在のノードの左側の子ノードを現在のノードとして採用します。
  • 12 を現在のノードのキー値 12 と比較すると、12 は 12 に等しい。条件が満たされる場合は、現在のノードからデータ、つまり id=12、name=xm を取り出します。

二分探索木を使用すると、一致するデータを見つけるのに 3 回しか必要ありません。テーブル内を 1 つずつ検索すると、見つけるまでに 6 回必要になります。

バランスの取れた二分木

上記では、二分探索木を使用するとデータをすばやく見つけることができることを説明しました。ただし、上記の二分探索木が次のように構築されているとします。

2

この時点で、二分探索木がリンク リストになっていることがわかります。id=17 のユーザー情報を見つける必要がある場合、7 回検索する必要があります。これはテーブル全体のスキャンに相当します。

この現象は、二分探索木のバランスが崩れ、高さが高くなりすぎて探索効率が不安定になることが原因です。

この問題を解決するには、二分探索ツリーが常にバランスがとれていることを確認する必要があるため、バランスのとれた二分ツリーを使用する必要があります。

平衡二分木はAVL木とも呼ばれ、二分探索木の特性を満たす上で、各ノードの左右の部分木の高さの差が1を超えないことが要求されます。

以下は、バランスのとれたバイナリ ツリーとアンバランスのバイナリ ツリーの比較です。

3

バランスのとれた二分木の構築から、最初の図の二分木が実際にはバランスのとれた二分木であることがわかります。

バランスの取れたバイナリ ツリーは、ツリーの構造のバランスが取れていることを保証します。データを挿入または削除してアンバランスのバイナリ ツリーのバランスが崩れると、バランスの取れたバイナリ ツリーはバランスを維持するためにツリー上のノードを調整します。具体的な調整方法はここでは紹介しません。

二分探索木と比較して、バランス二分木は探索効率がより安定し、全体的な探索速度が速くなります。

B ツリー

メモリの揮発性のため。通常の状況では、ユーザー テーブルのデータとインデックスをディスクなどの周辺デバイスに保存することを選択します。

ただし、ディスクからのデータの読み取り速度はメモリに比べて数百倍、数千倍、さらには一万倍も遅くなるため、ディスクからのデータの読み取り回数を減らすように努める必要があります。

また、ディスクからデータを読み取る場合、データは 1 つずつではなく、ディスク ブロックに従って読み取られます。

できるだけ多くのデータをディスク ブロックに入れることができれば、1 回のディスク読み取り操作でより多くのデータが読み取られるようになり、データを見つける時間が大幅に短縮されます。

インデックス データ構造としてツリー データ構造を使用する場合、データを検索するたびにディスクからノードを読み取る必要があります。これをディスク ブロックと呼びます。

バランスの取れたバイナリ ツリーには、ノードごとに 1 つのキー値とデータのみが格納されることは誰もが知っています。どういう意味ですか?各ディスク ブロックにはキー値とデータのみが保存されることを説明してください。では、大量のデータを保存したい場合はどうすればよいでしょうか?

二分木はノード数が多く高さが非常に高くなることが考えられますが、データを検索する際にはディスクIOも多く発生し、データ検索の効率が非常に低くなってしまいます。 !

4

バランスのとれたバイナリ ツリーのこの欠点を解決するには、単一のノードが複数のキー値とデータを格納できるバランスのとれたツリーを探す必要があります。それが次に説明する B ツリーです。

B-tree (Balance Tree) とはバランスのとれた木の意味で、次の図が B-tree です。

5

図中の p ノードは子ノードへのポインタですが、実際には二分探索木と平衡二分木がありますが、図の見栄えを考慮して省略しています。

図の各ノードはページと呼ばれ、ページは上で説明したディスク ブロックです。MySQL でのデータ読み取りの基本単位はページです。したがって、ここでページと呼ぶものは、基になるデータ構造とより一致しています。 MySQL のインデックス。

上図からわかるように、バランス二分木と比較して、B ツリーの各ノードはより多くのキー値 (key) とデータ (data) を格納し、各ノードにはより多くの子ノードがあり、子の数が増加します。ノードは一般に次数と呼ばれており、上図のBツリーは3次のBツリーであり、その高さは非常に低くなります。

この特徴に基づいて、B ツリーがデータを検索してディスクを読み取る回数は非常に少なくなり、データ検索効率は平衡型バイナリ ツリーよりもはるかに高くなります。

id=28 のユーザー情報を見つけたい場合、上図のツリー B での検索プロセスは次のようになります。

  • まず、ページ 1 であるルート ノードを見つけ、キー値 17 と 35 の間に 28 があると判断します。次に、ページ 1 のポインタ p2 に従ってページ 3 を見つけます。
  • 28 をページ 3 のキー値と比較すると、28 は 26 と 30 の間にあり、ページ 3 のポインタ p2 に従ってページ 8 が見つかります。
  • 28 とページ 8 のキー値を比較すると、一致するキー値 28 があり、キー値 28 に対応するユーザー情報は (28, bv) であることがわかります。

B+ ツリー 

6

上の図に従って、B+ ツリーと B ツリーの違いを見てみましょう。

① B+ ツリーの非リーフ ノードはデータを保存せず、キー値のみを保存しますが、B ツリー ノードはキー値を保存するだけでなくデータも保存します。

その理由は、データベースのページ サイズが固定されており、InnoDB のデフォルトのページ サイズが 16 KB であるためです。

データが保存されていない場合、より多くのキー値が保存され、対応するツリーの順序 (ノードの子ノード ツリー) が大きくなり、ツリーが短く太くなるため、次のデータを検索します。ディスクストレージ IO の数が再び減少し、データクエリの効率が速くなります。

また、B+ ツリーの次数はキー値の数に等しいため、B+ ツリーの 1 つのノードに 1000 個のキー値を格納できる場合、3 層の B+ ツリーには 1000×1000×1000 = 10 億のデータを格納できます。

一般に、ルート ノードはメモリ内に常駐するため、10 億のデータを検索するのに必要なディスク IO は 2 回だけです。

②B+ツリーインデックスのデータはすべてリーフノードに格納されており、データが順番に並んでいるからです。

次に、B+ ツリーを使用すると、範囲検索、ソート検索、グループ検索、重複排除検索が非常に簡単になります。しかし、B-treeのデータは各ノードに分散しているため、これを実現するのは容易ではありません。

興味のある読者は、上図の B+ ツリー内のページが二重リンク リストを通じて接続され、リーフ ノード内のデータが一方向リンク リストを通じて接続されていることにも気づくでしょう。

実際、上記の B ツリーの各ノードにリンク リストを追加することもできます。これは、MySQL の InnoDB ストレージ エンジンにインデックスが保存される方法であるため、以前との違いはありません。

つまり、上図の B+ ツリー インデックスは、InnoDB における B+ ツリー インデックスの実際の実装であり、正確には、クラスター化インデックスでなければなりません (クラスター化インデックスと非クラスター化インデックスについては後述します)。

上の図からわかるように、InnoDB では、二重リンク リストによるデータ ページ間の接続と、一方向リンク リストによるリーフ ノード内のデータ間の接続を通じて、テーブル内のすべてのデータを見つけることができます。 。

MyISAM の B+ ツリー インデックスの実装は、InnoDB の実装とは若干異なります。MyISAMでは、B+ツリーインデックスのリーフノードにはデータは格納されませんが、データのファイルアドレスが格納されます。

C++ で実装された B+ ツリー

B+ ツリーを実装する C++ の例:

#include<iostream>
#include<cstring>
#include<vector>
using namespace std;
 const int M = 4; // B+树的阶数
const int MM = 2 * M;
const int L = 5; // 叶节点能容纳的最大键值对数
 struct Node {
    bool leaf; // 是否为叶节点
    Node* parent; // 父节点
    Node* children[MM + 1]; // 子节点
    int values[MM]; // 记录值
    int cnt; // 子节点数或者键值对数
};
 class BPlusTree {
public:
    BPlusTree();
    ~BPlusTree();
     void insert(int value); // 插入记录
    void show(); // 显示整棵B+树
 private:
    void split(Node* x, int idx); // 对满节点分裂
    void insertNonfull(Node* x, int value); // 插入到非满节点
    void show(Node* x, int num); // 递归显示某个节点及其子树
     Node* root; // 根节点
};
 BPlusTree::BPlusTree() {
    root = NULL;
}
 BPlusTree::~BPlusTree() {
    // TODO
}
 void BPlusTree::split(Node* x, int idx) {
    Node* y = new Node();
    Node* z = x->children[idx];
    y->leaf = z->leaf;
    y->cnt = L;
    z->cnt = L;
     // 将z节点的后L个值移动到y节点
    memcpy(y->values, z->values + L, sizeof(int) * L);
    if(!y->leaf) {
        memcpy(y->children, z->children + L, sizeof(Node*) * (L + 1));
        for(int i = L; i < MM; i++) {
            z->children[i] = NULL;
        }
    }
    z->cnt = L;
     // 为父节点x插入键值,并将y节点加入到x的子节点中
    for(int i = x->cnt + 1; i > idx + 1; i--) {
        x->children[i] = x->children[i - 1];
    }
    x->children[idx + 1] = y;
    for(int i = x->cnt; i > idx; i--) {
        x->values[i] = x->values[i - 1];
    }
    x->values[idx] = z->values[L];
    x->cnt++;
}
 void BPlusTree::insertNonfull(Node* x, int value) {
    int i = x->cnt - 1;
     if(x->leaf) {
        while(i >= 0 && value < x->values[i]) {
            x->values[i + 1] = x->values[i];
            i--;
        }
        x->values[i + 1] = value;
        x->cnt++;
    }
    else {
        while(i >= 0 && value < x->values[i]) {
            i--;
        }
        i++;
        if(x->children[i]->cnt == MM) {
            split(x, i);
            if(value > x->values[i]) {
                i++;
            }
        }
        insertNonfull(x->children[i], value);
    }
}
 void BPlusTree::insert(int value) {
    if(root == NULL) {
        root = new Node();
        root->leaf = true;
        root->cnt = 1;
        root->values[0] = value;
    }
    else {
        if(root->cnt == MM) {
            Node* s = new Node();
            s->leaf = false;
            s->children[0] = root;
            split(s, 0);
            root = s;
            if(value > root->values[0]) {
                insertNonfull(root->children[1], value);
            }
            else {
                insertNonfull(root->children[0], value);
            }
        }
        else {
            insertNonfull(root, value);
        }
    }
}
 void BPlusTree::show() {
    if(root != NULL) {
        show(root, 0);
    }
}
 void BPlusTree::show(Node* x, int num) {
    cout << "(" << num << ")";
    for(int i = 0; i < x->cnt; i++) {
        cout << x->values[i] << " ";
    }
    cout << endl;
    if(!x->leaf) {
        for(int i = 0; i <= x->cnt; i++) {
            if(x->children[i] != NULL) {
                show(x->children[i], num + 1);
            }
        }
    }
}
 int main() {
    BPlusTree tree;
    for(int i = 0; i < 20; i++) {
        tree.insert(i);
    }
    tree.show();
     return 0;
}

SQLite の B+ ツリー関連のソース コード

1. B+ ツリー ノードの構造定義:

typedef struct MemPage MemPage;
typedef struct BtShared BtShared;
typedef struct BtCursor BtCursor;
 /* Page type flags */
#define PTF_LEAF        0x01
#define PTF_INTKEY      0x02
#define PTF_DATA        0x04
#define PTF_MKDUP       0x08
#define PTF_SQLITE_MASTER 0x10
#define PTF_ZERODATA    0x20  /* Leaf data is zeroed */
 /* Masks used for by various routines to inspect fields of the MemPage
** structure.  These #defines are not an internationally recognized
** standard, but are used here to make the code easier to read. */
#define PGNO(x)         ((x)->pgno)
#define HASPTRMAP(x)    ((x)->nFree>0)
#define ISLEAF(x)       ((x)->flags & PTF_LEAF)
#define ISINTKEY(x)     ((x)->flags & PTF_INTKEY)
#define ISDATA(x)       ((x)->flags & PTF_DATA)
#define ISNOERROR(x)    ((x)->errCode==0)
#define CellSize(p, i)  ((p)->cellOffset[(i)+1]-(p)->cellOffset[i])
 /* Handle to a database */
struct sqlite3 {
  sqlite3_mutex *mutex;          /* Connection mutex */
  sqlite3_vfs *pVfs;             /* OS Interface */
  struct Vdbe *pVdbe;            /* List of active virtual machines */
  CollSeq *pDfltColl;            /* Default collating sequence (BINARY) */
  int nCollNeeded;               /* Collating sequences needed to reparse SQL */
  CollSeq **apCollNeeded;        /* Collating sequence needed to compile SQL */
  int nVdbeActive;               /* Number of VDBEs currently executing */
  int nVdbeRead;                 /* Number of active VDBEs that read or write */
  int nVdbeWrite;                /* Number of active VDBEs that write */
  int nErr;                      /* Number of errors encountered */
  int errCode;                   /* Most recent error code (SQLITE_*) */
  sqlite3_vfs *pVfs2;            /* Secondary VFS */
  int activeVdbeCnt;             /* Number of VDBEs currently executing - VDBEs with sqlite3_stmt.nResColumn>0 or holding a lock */
  u32 writeVdbeCnt;              /* Counter incremented each time a VDBE writes */
  int (*xTrace)(u32,void*,void*,void*);      /* Trace function */
  void *pTraceArg;               /* Argument to the trace function */
  void (*xProfile)(void*,const char*,u64);  /* Profiling function */
  void *pProfileArg;             /* Argument to profile function */
  void *pCommitArg;              /* Argument to xCommitCallback() */
  int (*xCommitCallback)(void*); /* Invoked at every commit. */
  void *pRollbackArg;            /* Argument to xRollbackCallback() */
  void (*xRollbackCallback)(void*); /* Invoked at every commit. */
  void *pUpdateArg;              /* Argument to xUpdateCallback() */
  void (*xUpdateCallback)(void*,int, const char*,const char*,sqlite_int64);
  int nTransaction;              /* Transaction count.  0 means no transaction */
  int nStmtDefrag;               /* Number of previously allocated VDBE statements */
  struct Stmt *pStmtDefrag;      /* Compacting statement */
  FuncDefHash aFuncDef;          /* Hash table of connection functions */
  Hash aCollSeq;                 /* All collating sequences */
  BusyHandler busyHandler;       /* Busy callback */
  Db aDbStatic[2];               /* Static space for the 2 default backends */
  Savepoint *pSavepoint;         /* List of active savepoints */
  int busyTimeout;               /* Busy handler timeout, in msec */
  int nSavepoint;                /* Number of non-transaction savepoints */
  int *pnBytesFreed;             /* If not NULL, increment this in DbFree() */
  void **apSqlFuncArg;           /* Accumulated function args */
  int nSqlFuncArg;               /* Number of entries in apSqlFuncArg */
  VTable **pVTab;                /* Virtual tables with open transactions */
  int nVTab;                     /* Number of virtual tables with open transactions */
  KeyInfo *aKeyStat;             /* Statistics on index heights */
  char *zProfile;                /* Output of profiling */
  u32 u1;                        /* Unsigned integer value */
  u32 u2;                        /* Unsigned integer value */
  int lastRowid; /* ROWID of most recent insert (see above) */
  int nChange; /* Value returned by sqlite3_changes() */
  int nTotalChange; /* Value returned by sqlite3_total_changes() */
  int aLimit[SQLITE_N_LIMIT];    /* Limits */
  int nMaxSorterMmap;            /* Maximum size of regions mapped by sorter */
  int iPrngState;                /* PRNG running state */
  sqlite3_int64 counters[3];
  unsigned char *aCollNeeded[2]; /* Collating sequences required by current SQL */
  Lookaside lookaside;           /* Lookaside malloc configuration */
  int szMmap;                    /* mmap() limit */
  int bBenignMalloc;             /* Do not enforce malloc limits if true */
};
struct MemPage {
  u32 isInit:1;      /* True if previously initialized. MUST BE FIRST! */
  u32 nFree:15;      /* Number of free bytes on the page */
  u32 intKey:1;      /* True if the page contains integer keys */
  u32 intKeyLeaf:1;  /* True if the leaf of intKey table */
  u32 noPayload:1;   /* True if this page has no payload */
  u32 leaf:1;        /* True if this is a leaf page*/
  u32 hasData:1;     /* True if this page carries data */
  u32 hdrOffset:11;  /* Offset to the start of the page header */
  u16 maxLocal;      /* Copy of Btree.pageSize.  Max local payload size */
  u16 minLocal;      /* Copy of Btree.usableSize.  Min local payload size */
  u16 cellOffset[1]; /* Array of cell offsets.  Negative offsets indicate
                      ** unused slots.  Positive offsets are offsets from the
                      ** beginning of the page to the cell content. */
                      /* u8 aData[minLocal];*/ /* Beginning of payload area */
};

2. B+ツリーの主要機能の実現

/*
** Get a page from the pager.  Initialize the MemPage.pBt and
** MemPage.aData elements if needed.
**
** If the noContent flag is set, it means that we do not care about
** the content of the page at this time.  So do not go to any extra
** effort to read or write the content.  Just fill in the content
** size fields and return.
*/
static int getAndInitPage(
  BtShared *pBt,          /* The BTree that this page is part of */
  Pgno pgno,              /* Number of the page to get */
  MemPage **ppPage,       /* Write the page pointer here */
  int noContent           /* Do not load page content if true */
){
  MemPage *pPage = 0;     /* Page to return */
  int rc;                 /* Return Code */
  u8 eLock;               /* Lock to obtain on page */
  u8 intKey;              /* True if the btree is an int-key btree */
  int pgsz;               /* Size of a database page */
  Pager *pPager;          /* The pager from which to get the page */
  DbPage *pDbPage = 0;    /* Pager page handle */
  unsigned char *aData;   /* Data for the page */
  int usableSize;         /* Number of usable bytes on each page */
  u8 hdr[sizeof(MemPage)];/* Page One header content */
   assert( sqlite3_mutex_held(pBt->mutex) );
  pPager = pBt->pPager;
  usableSize = pBt->usableSize;
  pgsz = sqlite3PagerPageSize(pPager);
  if( pgno==1 ){
    rc = sqlite3PagerRead(pPager, 1, (void*)hdr);
    if( rc!=SQLITE_OK ) return rc;
    if( memcmp(hdr, zMagicHeader, sizeof(zMagicHeader))!=0 ){
      if( memcmp(hdr, zLegacyHeader, sizeof(zLegacyHeader))==0 ){
        return SQLITE_READONLY_RECOVERY;
      }
      return SQLITE_NOTADB;
    }
    pBt->pageSize = (hdr[16]<<8) | (hdr[17]<<16);;
    pBt->usableSize = usableSize = (hdr[20]<<8) | (hdr[21]<<16);;
    pBt->maxEmbedFrac = hdr[22];
    pBt->minEmbedFrac = hdr[23];
    pBt->minLeafFrac = hdr[24];
    pBt->pageSizeFixed = ((hdr[27]&0x02)!=0);
    if( !pBt->pageSizeFixed){
      assert( PENDING_BYTE_PAGE(pBt)==BTREE_PAGENO(pBt,1) );
      assert( sqlite3PagerRefcount(pPager, PENDING_BYTE_PAGE(pBt))==1 );
      rc = sqlite3PagerWrite(pPager, PENDING_BYTE_PAGE(pBt));
      if( rc ) return rc;
      memset(aData = sqlite3PagerGetData(pPage->pDbPage), 0, pgsz);
      aData[PENDING_BYTE_OFFSET(pBt)] = TRANS_WRITE;
      pBt->needSave = 1;
      if( pBt->inTransaction==TRANS_NONE ){
        assert( !pBt->readOnly );
        rc = sqlite3PagerBegin(pPager, 2, 0);
        if( rc!=SQLITE_OK ){
          return rc;
        }
        pBt->inTransaction = TRANS_WRITE;
      }
    }
    if( pBt->pageSize<512 || sqlite3BitvecSize((pBt->pageSize-1)/8+1)<=0 ){
      return SQLITE_CORRUPT_BKPT;
    }
    if( !pBt->btsFlags ){
      assert( sqlite3PagerRefcount(pPager, PENDING_BYTE_PAGE(pBt))==1 );
      rc = sqlite3PagerWrite(pPager, PENDING_BYTE_PAGE(pBt));
      if( rc ) return rc;
      aData = sqlite3PagerGetData(pDbPage);
      aData[PENDING_BYTE_OFFSET(pBt)] = TRANS_WRITE;
      pBt->inTransaction = TRANS_WRITE;
      rc = sqlite3PagerCommitPhaseOne(pPager, zMasterJournal, 0);
      if( rc ) return rc;
      pBt->btsFlags |= BTS_INITIALLY_EMPTY;
    }
  }
  if( pgno>btreePagecount(pBt) ){
    if( noContent ){
      *ppPage = 0;
      return SQLITE_OK;
    }
    rc = sqlite3PagerWrite(pPager, pgno);
    if( rc ) return rc;
    if( pBt->btsFlags & BTS_READ_ONLY ){
      return SQLITE_READONLY;
    }
    pDbPage = 0;
    rc = allocateBtreePage(pBt, &pDbPage, &pgno, 0, 1);
    if( rc!=SQLITE_OK ){
      return rc;
    }
    /* If the database supports shared cache AND the page is not the
    ** first page of a new database file, mark the page as an 
    ** int-cells page. There are two exceptions:
    **
    **   * If we are creating a new file and the user has configured the 
    **     system to use 8+ byte cell overflows (see below), the call to
    **     allocateBtreePage() will have already marked the page as an
    **     int-cells page. 
    **
    **   * If the page being requested is a leaf-data page, the
    **     allocateBtreePage() call always marked the page as a 
    **     leaf-data page. This takes priority over the setting below.
    **
    ** The calling function is responsible for setting pPage->intKey
    ** and pPage->intKeyLeaf appropriately.
    */
    if( ISAUTOVACUUM ){
      if( pgno>2 || (pgno==2 && (pBt->btsFlags & BTS_FIRST_KV_PAGE)==0) ){
        pDbPage->aData[0] = (u8)0x02;
      }
    }
  }else{
    rc = sqlite3PagerAcquire(pPager, pgno, &pDbPage, noContent);
    if( rc ) return rc;
  }
  aData = sqlite3PagerGetData(pDbPage);
  eLock = (noContent ? EXCLUSIVE_LOCK : pBt->inTransaction>TRANS_NONE);
  rc = sqlite3PagerSharedLock(pPager);
  if( rc!=SQLITE_OK ){
    sqlite3PagerUnref(pDbPage);
    return rc;
  }
  rc = btreeInitPage(pDbPage, eLock, pBt->pageSize);
  if( rc!=SQLITE_OK ){
    sqlite3PagerUnref(pDbPage);
    return rc;
  }
  if( pgno==1 ){
    memcpy(aData, hdr, pgsz);
    if( !pBt->pageSizeFixed ){
      aData[16] = (u8)(pBt->pageSize>>8);
      aData[17] = (u8)(pBt->pageSize>>16);
      aData[20] = (u8)(pBt->usableSize>>8);
      aData[21] = (u8)(pBt->usableSize>>16);
    }
  }
  pPage = new MemPage;
  if( !pPage ){
    rc = SQLITE_NOMEM;
    sqlite3PagerUnref(pDbPage);
    return rc;
  }
  pPage->aData = aData;
  pPage->pBt = pBt;
  pPage->pDbPage = pDbPage;
  pPage->pgno = pgno;
  pPage->usableSize = usableSize;
  if( pBt->btsFlags & BTS_READ_ONLY ){
    pPage->flags |= PAGER_GET_READONLY;
  }
  if( pgno==1 ){
    pPage->isInit = 1;
    pPage->nFree = usableSize
        - ((36+sqlite3GetNameSize(&pBt->zFilename[1])+7)&~7)
        - 100;
  }
  *ppPage = pPage;
  return SQLITE_OK;
}
 /*
** Read a 32-bit integer from a page and return its value.
** The value is stored in big-endian format.
*/
u32 get4byte(const void *pBuf){
  const u8 *p = (const u8*)pBuf;
  return ((u32)p[0]<<24) | (p[1]<<16) | (p[2]<<8) | p[3];
}
 /* The various locking operations are numbered so that we can test
** for locks in a bitvector
*/
#define READ_LOCK            0x01  /* Shared lock */
#define WRITE_LOCK           0x02  /* Exclusive lock */
 /* Return TRUE if the BtShared mutex is held by this thread at the point
** this routine is called.  This is used only assert() statements and
** testcase() macros.
*/
static int heldMutexSanityCheck(BtShared *pBt){
#ifndef SQLITE_DEBUG
  UNUSED_PARAMETER(pBt);
  return 1;
#else

その他のリソース

データ構造の可視化

B+ ツリーを理解する_b+ ツリーとは_Bobozai86 のブログ-CSDN ブログ

B TreeとB+ Treeの詳細説明

B+ ツリーは実際には難しくありません。B+ ツリーは下の階にいる全員が学ぶことができます。(データ構造可視化アーティファクトの推奨)_bplustree Visualization_Cai Caibu Cai のブログ-CSDN ブログ

b+ ツリーの詳細な説明 - プログラマが求めた

バイナリツリープログラミング問題集(leetcode)_JavaとPythonを学びたいブログ - CSDNブログ

B TreeとB+ Treeの詳細説明

https://huaweicloud.csdn.net/637ef533df016f70ae4ca698.html

おすすめ

転載: blog.csdn.net/qq8864/article/details/129723920