About the introduction, usage and c++ code implementation of B+ tree

The importance of data structures and algorithms is self-evident. The core and soul of some excellent open source projects are data structures and algorithms. In actual programming, we can often see B-trees and B+ trees in various frameworks and algorithms. Especially in the database engine of the database, they occupy an important position. Next, I will explain in detail the origin, function, operation, and practical application of multi-fork trees (B-trees, B+-trees) from simple binary trees to tree evolution.

Introduction to B+ tree

B+ tree is a type of data structure commonly used in database systems and file systems. Its main purpose is to store and index large amounts of data on disk to improve retrieval efficiency. B+ tree and B tree are very similar, but there is an important difference: B+ tree only stores data in leaf nodes, and non-leaf nodes only store index information. This structure makes the B+ tree better adapt to the disk reading method, because the cost of reading a record on the disk is very high. The leaf nodes of the B+ tree form a linked list, which can easily implement range queries.

They are designed to allow efficient searching and indexing of large datasets, especially those stored on disk. In a B+ tree, data is stored in nodes organized into a tree structure. Each node contains a series of keys and pointers to child nodes or data records. The root node of the tree contains pointers to child nodes, while the leaf nodes contain pointers to data records.

Advantages of B+ tree

One of the main advantages of B+ trees is that they allow efficient range queries, which are especially useful in database applications. This is because the keys in each node are sorted, which makes it easy to traverse the tree to find the desired data.

B+ trees are widely used in open source database projects, such as MySQL, PostgreSQL, SQLite, etc. Among them, the InnoDB storage engine in MySQL uses B+ tree as the index structure. In addition, file systems and key-value storage systems often use B+ trees as core data structures.

File systems such as NTFS, ReiserFS, NSS, XFS, JFS, ReFS, and BFS all use B+ trees as metadata indexes. The characteristic of the B+ tree is that it can keep the data stable and orderly, and its insertion and modification have a relatively stable logarithmic time complexity. B+ tree elements are inserted bottom-up.

B+ Tree Visualization

The tree shape (4th order B+ tree) is shown in the figure below:

insert image description here

The origin of B+ tree

The B+ tree is evolved from the three data structures of binary search tree , balanced binary tree and B tree. Starting from the binary tree, look at the evolution of the B+ tree. 

binary search tree

First, let's look at a picture:

​edit

As can be seen from the figure, we have established a binary search tree index for the user table (user information table).

The circle in the figure is the node of the binary search tree, and the key (key) and data (data) are stored in the node. The key corresponds to the id in the user table, and the data corresponds to the row data in the user table.

The characteristic of the binary search tree is that the key value of the left child node of any node is smaller than the key value of the current node, and the key value of the right child node is greater than the key value of the current node. The node at the top is called the root node, and the nodes without children are called leaf nodes.

If we need to find user information with id=12, using the binary search tree index we created, the search process is as follows:

  • Take the root node as the current node, compare 12 with the key value 10 of the current node, 12 is greater than 10, and then we take the current node > the right child node as the current node.
  • Continue to compare 12 with the key value 13 of the current node, and find that 12 is smaller than 13, and take the left child node of the current node as the current node.
  • Comparing 12 with the key value 12 of the current node, 12 is equal to 12. If the condition is met, we take out the data from the current node, that is, id=12, name=xm.

Using binary search tree we only need 3 times to find matching data. If we search one by one in the table, we need 6 times to find it.

balanced binary tree

Above we explained that using a binary search tree can quickly find data. However, if the binary search tree above is constructed like this:

2

At this time, we can see that our binary search tree has become a linked list. If we need to find the user information with id=17, we need to search 7 times, which is equivalent to a full table scan.

The reason for this phenomenon is that the binary search tree becomes unbalanced, that is, the height is too high, which leads to unstable search efficiency.

In order to solve this problem, we need to ensure that the binary search tree is always balanced, so we need to use a balanced binary tree.

A balanced binary tree is also called an AVL tree. On the basis of satisfying the characteristics of a binary search tree, it is required that the height difference between the left and right subtrees of each node cannot exceed 1.

The following is a comparison between a balanced binary tree and an unbalanced binary tree:

3

From the construction of the balanced binary tree, we can find that the binary tree in the first picture is actually a balanced binary tree.

The balanced binary tree ensures that the structure of the tree is balanced. When we insert or delete data and cause the unbalanced binary tree to be unbalanced, the balanced binary tree will adjust the nodes on the tree to maintain balance. The specific adjustment method will not be introduced here.

Compared with the binary search tree, the balanced binary tree has more stable search efficiency and faster overall search speed.

B-tree

Because of the volatility of memory. Under normal circumstances, we will choose to store the data and indexes in the user table in peripheral devices such as disks.

But compared with the memory, the speed of reading data from the disk will be hundreds of times, thousands of times or even ten thousand times slower, so we should try to reduce the number of times to read data from the disk.

In addition, when reading data from the disk, it is read according to the disk block, not one by one.

If we can put as much data as possible into disk blocks, more data will be read in one disk read operation, and the time for us to find data will be greatly reduced.

If we use the tree data structure as the index data structure, then we need to read a node from the disk every time we search for data, which is what we call a disk block.

We all know that a balanced binary tree stores only one key value and data per node. What does that mean? Explain that each disk block only stores a key value and data! So what if we want to store massive amounts of data?

It is conceivable that there will be many nodes in the binary tree, and the height will be extremely high. When we search for data, we will also perform a lot of disk IO, and the efficiency of our search for data will be extremely low!

4

In order to solve this shortcoming of a balanced binary tree, we should look for a balanced tree in which a single node can store multiple key values ​​and data. That is the B tree we are going to talk about next.

B-tree (Balance Tree) is the meaning of balanced tree, the following figure is a B-tree:

5

The p node in the figure is a pointer to a child node. In fact, there are binary search trees and balanced binary trees, which are omitted because of the aesthetics of the figure.

Each node in the figure is called a page, and a page is the disk block we mentioned above. The basic unit of data reading in MySQL is a page, so what we call a page here is more in line with the underlying data structure of the index in MySQL.

As can be seen from the above figure, compared with the balanced binary tree, each node of the B tree stores more key values ​​(key) and data (data), and each node has more child nodes, and the number of child nodes is generally It is called the order, and the B-tree in the above figure is a 3rd-order B-tree, and its height will be very low.

Based on this feature, the number of times B-trees search for data and read disks will be very small, and the data search efficiency will be much higher than that of balanced binary trees.

If we want to find the user information with id=28, then the process of searching in the tree B in the above figure is as follows:

  • First find the root node, which is page 1, and judge that 28 is between the key value 17 and 35, then we find page 3 according to the pointer p2 in page 1.
  • Comparing 28 with the key value in page 3, 28 is between 26 and 30, we find page 8 according to the pointer p2 in page 3.
  • Comparing 28 with the key value in page 8, it is found that there is a matching key value 28, and the user information corresponding to key value 28 is (28, bv).

B+ tree 

6

According to the above figure, let's look at the difference between B+ tree and B tree:

① B+ tree non-leaf nodes do not store data, only key values ​​are stored, while B tree nodes not only store key values, but also store data.

The reason for this is that the page size in the database is fixed, and the default page size in InnoDB is 16KB.

If the data is not stored, then more key values ​​will be stored, the corresponding tree order (the child node tree of the node) will be larger, and the tree will be shorter and fatter, so that we search for data for disk storage The number of IOs will be reduced again, and the efficiency of data query will be faster.

In addition, the order of the B+ tree is equal to the number of key values. If one node of our B+ tree can store 1000 key values, then the 3-layer B+ tree can store 1000×1000×1000=1 billion data.

Generally, the root node is resident in memory, so generally we only need 2 disk IOs to search for 1 billion data.

②Because all the data of the B+ tree index are stored in the leaf nodes, and the data is arranged in order.

Then the B+ tree makes range search, sort search, group search and deduplication search extremely simple. However, because the data of the B-tree is scattered in each node, it is not easy to achieve this.

Interested readers may also find that the pages in the B+ tree in the above figure are connected through a doubly linked list, and the data in the leaf nodes are connected through a one-way linked list.

In fact, we can also add a linked list to each node of the above B tree. These are not the differences they were before because that's how indexes are stored in MySQL's InnoDB storage engine.

In other words, the B+ tree index in the above figure is the real implementation of the B+ tree index in InnoDB. To be precise, it should be a clustered index (clustered index and non-clustered index will be discussed below).

As can be seen from the figure above, in InnoDB, we can find all the data in the table through the connection between the data pages through the doubly linked list and the connection between the data in the leaf nodes through the one-way linked list.

The B+ tree index implementation in MyISAM is slightly different from that in InnoDB. In MyISAM, the leaf nodes of the B+ tree index do not store data, but store the file address of the data.

B+ tree implemented by C++

Example of C++ implementing B+ tree:

#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;
}

B+ tree related source code in SQLite

1. The structure definition of B+ tree node:

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. Realization of key functions of B+ tree

/*
** 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

other resources

Data Structure Visualization

Understanding B+ tree_What is b+ tree_Bobozai86's Blog-CSDN Blog

Detailed Explanation of B Tree and B+ Tree

The B+ tree is really not difficult, the B+ tree can be learned by everyone downstairs! (Data structure visualization artifact recommendation)_bplustree visualization_Cai Caibu Cai's Blog-CSDN Blog

Detailed explanation of b+ tree - Programmer Sought

Binary tree programming problem collection (leetcode)_I want to learn java and python blog-CSDN blog

Detailed Explanation of B Tree and B+ Tree

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

Guess you like

Origin blog.csdn.net/qq8864/article/details/129723920