欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

AppBoxFuture: 二级索引及索引扫描查询数据

程序员文章站 2022-07-02 13:25:31
  数据库索引对于数据查询的重要性不可言喻,因此作者在存储层实现了二级索引,以及利用索引进行扫描的功能。目前仅实现了分区表与非分区表的本地索引(数据与索引共用一个Raft组管理),全局索引及反向索引待以后再实现。 一、存储结构:   在介绍索引前先了解一下数据 ......

  数据库索引对于数据查询的重要性不可言喻,因此作者在存储层实现了二级索引,以及利用索引进行扫描的功能。目前仅实现了分区表与非分区表的本地索引(数据与索引共用一个raft组管理),全局索引及反向索引待以后再实现。

一、存储结构:

  在介绍索引前先了解一下数据与索引是以何种结构存储于rocksdb内的,每个节点的rocksdb实例都包含以下两个columnfamily,每个列簇的存储结构如下:

1. tablecf: 存储实体数据

1.1 key存储编码:

tableid(with orderflag) entityid
32bit 128bit
  • orderflag用于entityid按升序还是降序排列
  • entityid包含实体创建时间戳,全局惟一

1.2 value存储编码:

versions gc flag [versionts + dataptr] [data [fieldid + value]]
15bit 1 bit versions * (64+32bit) versions * nbit
  • versions表示该条记录有多少个mvcc版本
  • gc flag表示第一个版本前是否被清理掉了
  • [versionts + dataptr]其中versionts是混合逻辑时间戳,dataptr指向此版本的数据位置,另外dataptr=0xffffffff表示记录删除标记
  • 数据部分每100个(暂定)保存一个fullversion的记录数据,后跟差异部分

1.3 dbscan工具输出示例:

AppBoxFuture: 二级索引及索引扫描查询数据

2. indexcf: 存储索引数据

2.1 key存储编码:

tableid indexid indexkey values none unique index's entityid
32bit 8bit [fieldid + value] 128bit
  • indexkey values中的fieldid有一位是排序标志位
  • none unique index's entityid表示非惟一索引指向的目标,惟一索引存在于value内

2.2 value存储编码:

  与tablecf的value编码相同。

2.3 dbscan工具输出示例:

AppBoxFuture: 二级索引及索引扫描查询数据

二、索引管理:

  在新建实体模型及修改实体模型时均可添加与删除索引(如下图所示),需要注意的是修改模型时添删索引会启用异步任务变更表结构并重建索引数据(请参考之前的文章:异步结构变更),如果是重建惟一索引可能失败,在实体模型设计器内可查看索引重建状态。在索引重建过程中或重建失败后利用索引扫描数据会直接报错,告知索引尚未准备好。
AppBoxFuture: 二级索引及索引扫描查询数据

三、索引扫描:

  不同于传统sql数据库解析sql后利用索引扫描,服务模型的代码必须明确指定用哪个索引来查询数据,具体参考以下示例代码如何利用索引扫描数据:

public async task<object> indexscan()
{
    //新建索引扫描,范型参数1为实体类型,参数2为索引类型
    var q = new indexscan<entities.vehiclestate, entities.vehiclestate.ix_vid_speed>();
    //如果是分区表可通过分区谓词指定分区扫描,非分区表无效
    q.partitions.equal(t => t.vid, 3);
    //可指定索引谓词确定扫描范围
    q.keys.equal(t => t.vid, 3);
    //如果是复合索引可指定其他索引谓词
    q.keys.equal(t => t.speed, 100);
    return await q.take(10).tolistasync();
}
  • 索引谓词目前仅实现了相等性判断,其他如大于、小于等稍后实现
  • 索引扫描的附加过滤条件尚未实现

  另如果需要插入一批测试数据可参考以下示例代码:

public async task<object> filldata()
{
    //第一个参数128表示并行任务数,第二个参数表示每个任务执行次数
    //作者虚拟机(i74c8g)执行以下代码约每秒插入13000条记录
    return await simpleperftest.run(128, 500, async (i, j) =>
    {
        var obj = new entities.vehiclestate(i);
        obj.speed = j;
        await entitystore.saveasync(obj);
    });
}

四、本篇小结:

  本篇介绍数据及索引的存储结构以及利用索引扫描api来查询数据,下一步作者将实现其他谓词条件,另外实现聚合扫描并优化单分区事务递交。github上的运行时已更新(包括dbscan工具)可供测试。如果您有问题或bug报告,请留言或提交issue,另外您的关注与点赞将是作者最大的动力。