乐趣区

关于mysql:实现一个简单Database6

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

前文回顾

实现一个简略的 Database1(译文)

实现一个简略的 Database2(译文)

实现一个简略的 Database3(译文)

实现一个简略的 Database4(译文)

实现一个简略的 Database5(译文)


译注:cstsck 在 github 保护了一个简略的、相似 sqlite 的数据库实现,通过这个简略的我的项目,能够很好的了解数据库是如何运行的。本文是第六篇,次要是实现数据长久化游标

Part 6 游标形象

跟上一节相比,这一节篇幅绝对要简短的多。咱们只是略微重构一下,这样就能够让开始实现 B -tree 更简略一些。

在代码中新增加一个 Cursor 对象,它用来代表表中的一个地位(location)。

你可能心愿对游标执行的操作:

  • 在表结尾时创立一个游标
  • 在表结尾时创立一个游标
  • 拜访游标指向的行
  • 将游标挪动到下一行

这就是咱们将要实现的游标的一些行为。而后,咱们还想做到:

  • 删除游标指向的一行数据
  • 批改游标指向的一行数据
  • 应用给定的 ID 搜寻一张表,并创立一个游标指向这个 ID 所在的行

_译注:这里简略介绍一下游标,Cursor 本来就有箭头、光标的意思,用来批示事物以示关注。游标是数据的一种拜访机制,一种解决数据的办法,例如查问返回的后果是一行或者多行的后果集(这曾经是 SQL 被解决后的后果集),咱们须要对后果集进行查问,sql 语句就不论用了,因为这曾经是返回的后果集了,这个时候就须要用游标来遍历这个返回的后果集了。能够了解游标是一个指向 Row 的指针,拜访一行后,游标就会指向下一行。例如 fetchone()、fetchall() 等函数就是通过游标来拜访后果集的,返回具体一行或者多行的数据。上面是游标图示:

不磨叽了,上面就是 Cursor 类型构造:

+typedef struct {
+  Table* table;
+  uint32_t row_num;
+  bool end_of_table;  // Indicates a position one past the last element
+} Cursor;

依据当初咱们的表数据结构,只须要行号即可辨认表中的地位。

游标对它所属的表还有一个援用(所以咱们的游标函数还能够仅仅把游标作为参数)。

最初,它还有一个 boolean 类型的属性叫做 end_of_table。这样咱们就能够用来示意表结尾之后的地位(在这里咱们可能会插入一个新行)。

table_start() 和 table_end() 创立一个新的游标:

+Cursor* table_start(Table* table) {+  Cursor* cursor = malloc(sizeof(Cursor));
+  cursor->table = table;
+  cursor->row_num = 0;
+  cursor->end_of_table = (table->num_rows == 0);
+
+  return cursor;
+}
+
+Cursor* table_end(Table* table) {+  Cursor* cursor = malloc(sizeof(Cursor));
+  cursor->table = table;
+  cursor->row_num = table->num_rows;
+  cursor->end_of_table = true;
+
+  return cursor;
+}

咱们的 row_slot() 函数要变成 cursor_value() 了,它返回一个指针类型,指向游标形容的地位:

-void* row_slot(Table* table, uint32_t row_num) {+void* cursor_value(Cursor* cursor) {
+  uint32_t row_num = cursor->row_num;
   uint32_t page_num = row_num / ROWS_PER_PAGE;
-  void* page = get_page(table->pager, page_num);
+  void* page = get_page(cursor->table->pager, page_num);
   uint32_t row_offset = row_num % ROWS_PER_PAGE;
   uint32_t byte_offset = row_offset * ROW_SIZE;
   return page + byte_offset;
 }

在咱们以后的表构造中推动游标就像让行号递增一样简略。这在 B -tree 书中会有一点小简单。

+void cursor_advance(Cursor* cursor) {
+  cursor->row_num += 1;
+  if (cursor->row_num >= cursor->table->num_rows) {
+    cursor->end_of_table = true;
+  }
+}  

最初咱们就能扭转咱们的“虚拟机”办法来应用游标形象了。当插入一行数据时,咱们在表结尾关上一个游标,写入这个游标的地位,而后敞开游标。

Row* row_to_insert = &(statement->row_to_insert);
+  Cursor* cursor = table_end(table);

-  serialize_row(row_to_insert, row_slot(table, table->num_rows));
+  serialize_row(row_to_insert, cursor_value(cursor));
table->num_rows += 1;

+  free(cursor);
+
return EXECUTE_SUCCESS;
}

当查问表中的所有行时,咱们在表的结尾关上一个游标,打印行数据,而后推动游标到下一行,反复这个过程直到表的结尾。

 ExecuteResult execute_select(Statement* statement, Table* table) {+  Cursor* cursor = table_start(table);
+
   Row row;
-  for (uint32_t i = 0; i < table->num_rows; i++) {-    deserialize_row(row_slot(table, i), &row);
+  while (!(cursor->end_of_table)) {+    deserialize_row(cursor_value(cursor), &row);
     print_row(&row);
+    cursor_advance(cursor);
   }
+
+  free(cursor);
+
   return EXECUTE_SUCCESS;
 }

好了,就是它!就像我说的,这是短小的重构,应该可能有助于咱们把表的数据结构重写为 B -tree。execute_select() 和 execute_insert() 函数齐全能够通过游标来与表交互,而对于表的存储形式不须要假如任何事件。

译注:在之前实现数据库的 page 组织形式的代码中,作者应用数组(array)来组织数据页 page,次要是思考疾速实现存放数据,没有思考性能优化。前面会应用 B -tree 来进行重构,而在此之前,先实现了游标。_

这是于上一部分比照,代码的不同:

@@ -78,6 +78,13 @@ struct { } Table;

+typedef struct {
+  Table* table;
+  uint32_t row_num;
+  bool end_of_table; // Indicates a position one past the last element
+} Cursor;
+
 void print_row(Row* row) {printf("(%d, %s, %s)\n", row->id, row->username, row->email);
 }
@@ -126,12 +133,38 @@ void* get_page(Pager* pager, uint32_t page_num) {return pager->pages[page_num];
 }

-void* row_slot(Table* table, uint32_t row_num) {
-  uint32_t page_num = row_num / ROWS_PER_PAGE;
-  void *page = get_page(table->pager, page_num);
-  uint32_t row_offset = row_num % ROWS_PER_PAGE;
-  uint32_t byte_offset = row_offset * ROW_SIZE;
-  return page + byte_offset;
+Cursor* table_start(Table* table) {+  Cursor* cursor = malloc(sizeof(Cursor));
+  cursor->table = table;
+  cursor->row_num = 0;
+  cursor->end_of_table = (table->num_rows == 0);
+
+  return cursor;
+}
+
+Cursor* table_end(Table* table) {+  Cursor* cursor = malloc(sizeof(Cursor));
+  cursor->table = table;
+  cursor->row_num = table->num_rows;
+  cursor->end_of_table = true;
+
+  return cursor;
+}
+
+void* cursor_value(Cursor* cursor) {
+  uint32_t row_num = cursor->row_num;
+  uint32_t page_num = row_num / ROWS_PER_PAGE;
+  void *page = get_page(cursor->table->pager, page_num);
+  uint32_t row_offset = row_num % ROWS_PER_PAGE;
+  uint32_t byte_offset = row_offset * ROW_SIZE;
+  return page + byte_offset;
+}
+
+void cursor_advance(Cursor* cursor) {
+  cursor->row_num += 1;
+  if (cursor->row_num >= cursor->table->num_rows) {
+    cursor->end_of_table = true;
+  }
 }

 Pager* pager_open(const char* filename) {@@ -327,19 +360,28 @@ ExecuteResult execute_insert(Statement* statement, Table* table) { }

   Row* row_to_insert = &(statement->row_to_insert);
+  Cursor* cursor = table_end(table);

-  serialize_row(row_to_insert, row_slot(table, table->num_rows));
+  serialize_row(row_to_insert, cursor_value(cursor));
   table->num_rows += 1;

+  free(cursor);
+
   return EXECUTE_SUCCESS;
 }

 ExecuteResult execute_select(Statement* statement, Table* table) {+  Cursor* cursor = table_start(table);
+
   Row row;
-  for (uint32_t i = 0; i < table->num_rows; i++) {-     deserialize_row(row_slot(table, i), &row);
+  while (!(cursor->end_of_table)) {+     deserialize_row(cursor_value(cursor), &row);
      print_row(&row);
+     cursor_advance(cursor);
   }
+
+  free(cursor);
+
   return EXECUTE_SUCCESS;
 }

Enjoy GreatSQL :)

## 对于 GreatSQL

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

相干链接:GreatSQL 社区 Gitee GitHub Bilibili

GreatSQL 社区:

欢送来 GreatSQL 社区发帖发问
https://greatsql.cn/

技术交换群:

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

退出移动版