当前位置: 首页 > news >正文

数据库的范围查询

在这里插入图片描述

范围查询

B+树迭代器

迭代器接口

B+树的基本操作包括用于范围查询的查找和迭代。B+树的位置由状态化的迭代器 BIter 表示。

// 查找小于或等于输入键的最近位置
func (tree *BTree) SeekLE(key []byte) *BIter// 获取当前键值对
func (iter *BIter) Deref() ([]byte, []byte)// Deref() 的前置条件检查
func (iter *BIter) Valid() bool// 向前或向后移动
func (iter *BIter) Prev()
func (iter *BIter) Next()

例如,对于查询 a <= key 的操作可以这样实现:

for iter := tree.SeekLE(key); iter.Valid(); iter.Prev() {k, v := iter.Deref()// ...
}
导航树结构

为了在节点内找到当前键的兄弟键,需要知道当前键的位置。如果兄弟键位于兄弟节点中,则需要回溯到父节点。由于不使用父指针,因此需要从根到叶的完整路径。

type BIter struct {tree *BTreepath []BNode // 根到叶的路径pos  []uint16 // 节点中的索引
}

移动迭代器类似于逐位递增数字时的进位操作。

func (iter *BIter) Next() {iterNext(iter, len(iter.path)-1)
}func iterNext(iter *BIter, level int) {if iter.pos[level]+1 < iter.path[level].nkeys() {iter.pos[level]++ // 在当前节点内移动} else if level > 0 {iterNext(iter, level-1) // 移动到兄弟节点} else {iter.pos[len(iter.pos)-1]++ // 超出最后一个键return}if level+1 < len(iter.pos) { // 更新子节点node := iter.path[level]kid := BNode(iter.tree.get(node.getPtr(iter.pos[level])))iter.path[level+1] = kiditer.pos[level+1] = 0}
}
查找键

查找键类似于点查询,但会记录路径。

func (tree *BTree) SeekLE(key []byte) *BIter {iter := &BIter{tree: tree}for ptr := tree.root; ptr != 0; {node := tree.get(ptr)idx := nodeLookupLE(node, key)iter.path = append(iter.path, node)iter.pos = append(iter.pos, idx)ptr = node.getPtr(idx)}return iter
}

nodeLookupLE 是用于小于等于比较的函数,你还需要其他比较操作符。

const (CMP_GE = +3 // >=CMP_GT = +2 // >CMP_LT = -2 // <CMP_LE = -3 // <=
)func (tree *BTree) Seek(key []byte, cmp int) *BIter

9.2 保持顺序的编码

对任意数据进行字节字符串排序

我们的B+树处理的是任意字节的字符串键,但列可以是其他类型的数据,如数字,而且键可以是多个列。为了支持范围查询,序列化后的键必须根据其数据类型进行比较。

一种明显的方法是用一个回调函数替换 bytes.Compare,该回调函数根据表模式解码并比较键。另一种方法是选择一个特殊的序列化格式,使得生成的字节能够反映排序顺序。这是我们将采用的快捷方式。

数字

首先解决一个简单的问题:如何编码无符号整数,以便可以通过 bytes.Compare 比较它们?bytes.Compare 逐字节工作直到遇到差异。因此,在比较中第一个字节是最显著的。如果我们首先放置整数的最高有效(高位)位,那么它们就可以逐字节比较了。这正是大端整数。

接下来考虑有符号整数,它通过补码表示。在补码表示中,无符号值的上半部分只是偏移到负值。为了确保正确的顺序,正半部分与负半部分交换,这仅仅是翻转最高有效位。

var buf [8]byte
u := uint64(v.I64) + (1 << 63)
// 翻转符号位
binary.BigEndian.PutUint64(buf[:], u) // 大端

一些例子:

int64   | Encoded bytes
--------|-----------------
MinInt64| 00 00 00 00 00 00 00 00
-2      | 7f ff ff ff ff ff ff fe
-1      | 7f ff ff ff ff ff ff ff
0       | 80 00 00 00 00 00 00 00
1       | 80 00 00 00 00 00 00 01
MaxInt64| ff ff ff ff ff ff ff ff

一般思路:

  • 排列比特,使更高有效位先出现(大端)。
  • 将比特重新映射为按正确顺序排列的无符号整数。

练习:将此应用于浮点数(符号+量级+指数)。

字符串

键可以是多列,但是 bytes.Compare 只适用于单个字符串列,因为它需要长度。我们不能简单地连接字符串列,因为这会造成歧义。例如,(“a”, “bc”) vs (“ab”, “c”)。

有两种编码带有长度的字符串的方式,一种是在前面添加长度,这需要解码;另一种是在末尾放一个分隔符,比如空字节。前面的例子编码为 "a\x00bc\x00""ab\x00c\x00"

分隔符的问题在于输入中不能包含分隔符,这通过转义分隔符来解决。我们将使用字节 0x01 作为转义字节,并且转义字节本身也必须被转义。所以我们需要两种转换:

00 -> 01 01
01 -> 01 02

注意,转义序列仍然保留排序顺序。

元组

多列比较(元组)是逐列进行的,直到遇到差异为止。这就像字符串比较一样,除了每个项是一个类型值而不是字节。只要没有歧义,我们可以简单地串联每个列的编码字节。

9.3 范围查询

扫描器(Scanner)

扫描器是B+树迭代器的一个封装,它将键值对解码为行数据。

  • Valid():判断当前是否在范围内。
  • Next():移动底层的B树迭代器。
  • Deref(rec *Record):获取当前行。
  • db.Scan(table string, req *Scanner) error:执行范围查询并返回结果。
// 是否在范围内?
func (sc *Scanner) Valid() bool// 移动底层的B树迭代器
func (sc *Scanner) Next()// 获取当前行
func (sc *Scanner) Deref(rec *Record)func (db *DB) Scan(table string, req *Scanner) error

输入是一个主键的区间。Scanner 结构体定义如下:

type Scanner struct {// 范围,从Key1到Key2Cmp1 int // CMP_?? 比较操作符Cmp2 intKey1 Record // 起始键Key2 Record // 结束键// ...
}

对于开区间的情况,只需将 Key2 设置为最大或最小值即可。

9.4 我们学到了什么

  • B+树迭代器:我们了解了如何使用状态化的迭代器遍历B+树,包括查找特定键、向前向后移动等操作。
  • 保持顺序的编码:学习了如何对不同类型的数据进行编码,以确保它们在字节级别上保持正确的顺序。这对于支持范围查询至关重要。

下一步将是添加二级索引,这实际上就是额外的表结构,用于加速某些类型的查询。例如,一个二级索引可以用来快速查找符合特定条件的所有记录,而不需要遍历整个主表。这涉及到创建新的表来存储索引信息,并实现相应的查询逻辑以便于利用这些索引来提高查询效率。

代码仓库地址:database-go

相关文章:

  • brep2seq kaggle安装 micromamba conda环境
  • 进程间通信——管道
  • 计算机体系结构 第九章 (附带移数网络直径证明和取值情况)
  • 超详细BAM/SAM详解:文件格式与具体参数
  • Milvus(14):更改 Collections 字段、Schema 设计实践
  • Leetcode刷题记录33——二叉树的最小深度
  • 谈判模拟器提示词设计 - Gemini 2.5 优化版
  • Agent2Agent(谷歌A2A)协议原理讲解
  • CatBoost算法原理及Python实现
  • 牛客 Wall Builder II 题解
  • DeepSeek-提示词工程
  • 形式化数学——Lean的介绍与安装
  • Python异步编程入门:从同步到异步的思维转变
  • 链表操作练习
  • 【C++】WSL常用语法
  • 电子商务商家后台运营专员模板
  • Android工厂模式
  • 设一个测试情境,新用户注册后显示的名字不完整,测试思路是怎么样的?
  • 【C/C++】inline关键词
  • 用网页显示工控仪表
  • 中国医药科技出版社回应发布“男性患子宫肌瘤”论文:正在核查
  • 胖东来关闭官网内容清空?工作人员:后台维护升级
  • 抗战回望19︱《中国工程师学会四川考察团报告》:“将来重工业所在,以四川为最适宜之地点”
  • 山东如意集团及实控人等被出具警示函,此前实控人已被罚十年禁止入市
  • “五一”假期国内出游3.14亿人次,同比增长6.4%
  • 观察|“离境退税”撬动上海“五一”假期入境消费