• GreatSQL社区原创内容未经受权不得随便应用,转载请分割小编并注明起源。
  • GreatSQL是MySQL的国产分支版本,应用上与MySQL统一。
  • 作者: 花家舍
  • 文章起源:GreatSQL社区原创

前文回顾

  • 实现一个简略的Database系列

译注:cstack在github保护了一个简略的、相似sqlite的数据库实现,通过这个简略的我的项目,能够很好的了解数据库是如何运行的。本文是第九篇,次要是实现B-tree的二叉搜寻并解决主键反复问题

Part 9 二叉搜寻与主键反复

上次留神到咱们的 B 树存储 key 时依然是非排序的。当初咱们来解决这个问题,并检测和回绝主键的反复(插入)。

当初咱们的 execute_insert() 函数在插入时,抉择的地位是在表的结尾处。作为替换,咱们须要搜寻表(树)中正确的插入地位,而后把 key 插入到这个地位上。如果 key 在这个地位上曾经存在,那么就返回(键反复)报错。

ExecuteResult execute_insert(Statement* statement, Table* table) {   void* node = get_page(table->pager, table->root_page_num);-  if ((*leaf_node_num_cells(node) >= LEAF_NODE_MAX_CELLS)) {+  uint32_t num_cells = (*leaf_node_num_cells(node));+  if (num_cells >= LEAF_NODE_MAX_CELLS) {     return EXECUTE_TABLE_FULL;   }   Row* row_to_insert = &(statement->row_to_insert);-  Cursor* cursor = table_end(table);+  uint32_t key_to_insert = row_to_insert->id;+  Cursor* cursor = table_find(table, key_to_insert);++  if (cursor->cell_num < num_cells) {+    uint32_t key_at_index = *leaf_node_key(node, cursor->cell_num);+    if (key_at_index == key_to_insert) {+      return EXECUTE_DUPLICATE_KEY;+    }+  }   leaf_node_insert(cursor, row_to_insert->id, row_to_insert);

这样的话,咱们就不再须要 table_end() 函数了。

-Cursor* table_end(Table* table) {-  Cursor* cursor = malloc(sizeof(Cursor));-  cursor->table = table;-  cursor->page_num = table->root_page_num;--  void* root_node = get_page(table->pager, table->root_page_num);-  uint32_t num_cells = *leaf_node_num_cells(root_node);-  cursor->cell_num = num_cells;-  cursor->end_of_table = true;--  return cursor;-}

咱们用一个办法替换 table_end() 函数,办法实现应用给定的 key 来搜寻 Btree。

+/*+Return the position of the given key.+If the key is not present, return the position+where it should be inserted+*/+Cursor* table_find(Table* table, uint32_t key) {+  uint32_t root_page_num = table->root_page_num;+  void* root_node = get_page(table->pager, root_page_num);++  if (get_node_type(root_node) == NODE_LEAF) {+    return leaf_node_find(table, root_page_num, key);+  } else {+    printf("Need to implement searching an internal node\n");+    exit(EXIT_FAILURE);+  }+}

我先把外部节点实现的分支存根,因为我还没有实现外部节点(internal node)。咱们能够在叶子节点中应用二叉搜寻法来进行搜寻。

+Cursor* leaf_node_find(Table* table, uint32_t page_num, uint32_t key) {+  void* node = get_page(table->pager, page_num);+  uint32_t num_cells = *leaf_node_num_cells(node);++  Cursor* cursor = malloc(sizeof(Cursor));+  cursor->table = table;+  cursor->page_num = page_num;++  // Binary search+  uint32_t min_index = 0;+  uint32_t one_past_max_index = num_cells;+  while (one_past_max_index != min_index) {+    uint32_t index = (min_index + one_past_max_index) / 2;+    uint32_t key_at_index = *leaf_node_key(node, index);+    if (key == key_at_index) {+      cursor->cell_num = index;+      return cursor;+    }+    if (key < key_at_index) {+      one_past_max_index = index;+    } else {+      min_index = index + 1;+    }+  }++  cursor->cell_num = min_index;+  return cursor;+}

这将返回:

  • key 的地位
  • 如果咱们须要插入一个新的 key,须要挪动 key 的话,返回这个 key 的地位
  • 超出最初一个 key 的地位

因为咱们当初会查看节点的类型,所以须要一个函数在节点中获取和设置节点的类型值。

+NodeType get_node_type(void* node) {+  uint8_t value = *((uint8_t*)(node + NODE_TYPE_OFFSET));+  return (NodeType)value;+}++void set_node_type(void* node, NodeType type) {+  uint8_t value = type;+  *((uint8_t*)(node + NODE_TYPE_OFFSET)) = value;+}

须要先把值转为 uint8_t 类型来确保它作为一个序列化的单字节。 还须要初始化节点的类型。

-void initialize_leaf_node(void* node) { *leaf_node_num_cells(node) = 0; }+void initialize_leaf_node(void* node) {+  set_node_type(node, NODE_LEAF);+  *leaf_node_num_cells(node) = 0;+}

最初,咱们须要创立并解决一个新的错误码。

-enum ExecuteResult_t { EXECUTE_SUCCESS, EXECUTE_TABLE_FULL };+enum ExecuteResult_t {+  EXECUTE_SUCCESS,+  EXECUTE_DUPLICATE_KEY,+  EXECUTE_TABLE_FULL+};case (EXECUTE_SUCCESS):  printf("Executed.\n");  break;+      case (EXECUTE_DUPLICATE_KEY):+        printf("Error: Duplicate key.\n");+        break;case (EXECUTE_TABLE_FULL):  printf("Error: Table full.\n");  break;

有了这些批改,咱们的测试能够更改为查看排序程序:

"db > Executed.","db > Tree:","leaf (size 3)",-      "  - 0 : 3",-      "  - 1 : 1",-      "  - 2 : 2",+      "  - 0 : 1",+      "  - 1 : 2",+      "  - 2 : 3","db > "])end

并且咱们可能为反复键减少一个新的测试:

+  it 'prints an error message if there is a duplicate id' do+    script = [+      "insert 1 user1 person1@example.com",+      "insert 1 user1 person1@example.com",+      "select",+      ".exit",+    ]+    result = run_script(script)+    expect(result).to match_array([+      "db > Executed.",+      "db > Error: Duplicate key.",+      "db > (1, user1, person1@example.com)",+      "Executed.",+      "db > ",+    ])+  end

就是这样!接下来:实现拆分叶节点和创立外部节点。**


Enjoy GreatSQL :)

## 对于 GreatSQL

GreatSQL是由万里数据库保护的MySQL分支,专一于晋升MGR可靠性及性能,反对InnoDB并行查问个性,是实用于金融级利用的MySQL分支版本。

相干链接: GreatSQL社区 Gitee GitHub Bilibili

GreatSQL社区:

社区博客有奖征稿详情:https://greatsql.cn/thread-10...

技术交换群:

微信:扫码增加GreatSQL社区助手微信好友,发送验证信息加群