数据库设计中有一项至关重要的技术难点,那就是给定特定条件进行查询时,我们需要保证速度尽可能快。假设我们有一个 STUDENT 表,表中包含学生名字,年龄,专业等字段,当我们要查询给定年龄数值的记录,如果我们能把所有记录以年龄字段排序,那么通过二分查找,我们就能快速定位满足条件的记录。如果表中包含N=1,000,000 条记录,通过二分查找就能通过大概 logN = 20 次即可,但是要遍历所有记录来找,就得查询一百万次。
但根据某个字段来排序记录,当查询以另外字段查询时就无法得到相应加速。因此如何通过合适算法,让数据进行相应组织,使得查询根据不同字段进行时都能得到相应加速是数据库设计的核心难题。
在不改变表结构的情况下,要能根据不同字段加快查询速度,就需要索引制度。索引本质是一个文件,其中的数据是对给定表的记录分布进行描述和说明。索引文件中的数据以一条条记录的方式存在,一条记录包含两个字段,一个字段叫 datarid,它是一个指针,指向某条特定数据所在的位置,另一个字段叫 dataval,它可以是建立索引的字段的值,如果我们要想针对两个字段 age, name 进行索引,那么索引文件钟就包含两种记录,一种记录的 dataval 对应 age 的值,另一种记录的 dataval 存储 name 的值,然后记录根据 dataval 的值排序,于是我们要根据 age 查询时,我们先通过折半查找在第一种记录钟查询到 VALUE 为给定查询值的记录,然后通过 datarid 字段获取给定记录在硬盘的位置,另外需要注意的是,索引信息也是存储在文件中,获取索引信息也需要访问磁盘,因此我们需要使用好的所有算法尽可能减少在查询索引信息时对磁盘区块的读取。
使用索引文件创建索引数据来记录每条记录的位置还有一个问题,那就是记录会删除和修改,一旦相关记录删除和修改后,索引中的数据就需要进行相应变动,这里我们就需要 B 树,B+树等相关算法的支持。
还需要注意的是,一旦能快速定位所需记录的位置,我们就能定位记录所在的区块从而大大减少对硬盘的访问次数。但也有例外的情况,当建立索引的字段取值重复性很小时,索引的效率就好,如果索引字段取值的重复性很高,那么效率反而有可能会降低。
我们把索引建立的基本流程展示如下:
1,当解释执行索引建立的 SQL 语句时,例如 create majoridIDX on student (majorid),
create nameIDX on student (name), 启动索引建立流程
2,索引流程首先创建专门存储索引信息的表 idxcat,其字段为 indexname, tablename, fildname,这些字段分别用于存储索引的名称,建立索引的表名和字段名称。
3,选择索引算法,这里我们先使用前面说的哈希索引。我们使用一个哈希函数hash,假设他能将输入的数据哈希到 0-1000 这范围内的整数值, 假设字段 majorid 的取值 20和 100 经过该函数后输出结果相同为 39,那么代码将创建一个名为 majoroid#39 的记录表来存储这两条记录的访问信息(blockid 和 offset),上图,该表的字段为 dataval,block, id 分别用于存储记录对应索引字段的值,记录所在的 blockid 和 offset 也就是偏移。
在上面例子中由于 majorid 为 20 和 100 的记录都哈希到数值 39,因此他们这两条记录的存储信息都存储在表 majorid#39 这个表中,记录中字段为 name=”jim”的记录,由于”jim”哈希的结果为 69,因此该记录的存储信息放置在表 name#69 中。
哈希索引的效率取决于所寻求哈希函数的取值范围,假设函数哈希结果取值范围为 0-N,那么对于一个包含 M 条记录的的表,他对应记录的存储信息将放置在 M/N 个哈希记录表中。哈希索引法在理论上能将记录的查询时间从 M 优化到 M/N。
4,在执行 select 语句进行记录查询时,首先在索引表 idxcat 中查询给定表和对应字段是否已经建立了索引,如果建立了,那么直接从对应的查询记录表中获得记录的存储信息,然后直接从硬盘读取记录数据。
我们看对应代码实现,索引管理器的实现依然放在 metadata_manager 路径下,创建一个名为index_manager.go 的文件,增加代码如下:
package metadata_manager
//索引管理器需要使用到后面才讲到的SQL查询和索引算法知识,所以先放一放
import (
"query"
rm "record_manager"
"tx"
)
type IndexManager struct {
layout *rm.Layout
tblMgr *TableManager
statMgr *StatManager
}
func NewIndexManager(isNew bool, tblMgr *TableManager, statMgr *StatManager, tx *tx.Transation) *IndexManager {
if isNew {
//索引元数据表包含三个字段,索引名,对应的表名,被索引的字段名
sch := rm.NewSchema()