索引

索引是一种提高数据访问效率的特殊对象。在没有索引辅助的时候,如果要对少量记录进行精确查询,需要逐行地匹配扫描集合中所有的记录,这种方式的效率显然比较低。而有索引时,可以通过特定字段的值快速定位到匹配的记录,精确查询的效率将会大大提升。

使用

以下基于 JSON API 介绍索引的使用。

创建索引

要为集合创建索引,可以参考接口 SdbCollection.createIndex(<name>,<indexDef>,[options])。接口语法如下:

  1. createIndex(<索引名>, { <字段1>: <1|-1>, [<字段2>: <1|-1>...] },
  2. {
  3. [Unique: <true|false>],
  4. [Enforced: <true|false>],
  5. [NotNull: <true|false>],
  6. [SortBufferSize: <缓存大小数值>]
  7. })

例如,在 sample.employee 集合上为 id 字段建立 idIdx 索引:

  1. db.sample.employee.createIndex('idIdx', { 'id': 1 });

一个集合可以拥有多个索引,一个索引也可以拥有多个字段。详细规格可以参考数据库限制

  • 正序与倒序索引

    指定索引排序的顺序,在合适的场景下,会提升索引查找的效率。使用正序索引时,按索引字段正序排序的查找会更快,因为无需将匹配的记录再次进行排序。在匹配值小的记录时,也可能更快地命中。同理,如果有需要索引字段倒序排序的查询,或者经常需要匹配值较大的记录时,则适合使用倒序的索引。

    在创建索引的接口中,索引定义指定字段 1 为正序,-1 为倒序。例如,在 sample.employee 集合上为 birthdate 字段建立倒序的索引:

    1. db.sample.employee.createIndex('dateIdx', { 'birthdate': -1 });

    字段的值在类型相同时,按类型比较规则对比大小。而字段的值在类型不同时,则按类型优先级权值比较大小。如 {‘a’: 1} < {‘a’: 2},{‘a’: 1} < {‘a’: ‘1’}。

  • 唯一索引

    如果需要确保索引字段的值是唯一的,可以使用唯一索引。使用唯一索引时,如果插入或更新会产生重复的值,则会报错。创建索引时指定 Unique 选项为 true,即可创建唯一索引。例如,为 id 字段创建唯一索引。

    1. db.sample.employee.createIndex('idUniqueIdx', { 'id': 1 }, { 'Unique': true })

    默认地,唯一索引允许多个空值(null)同时存在。如果只允许空值唯一存在,可以附加指定 Enforced 选项为 true。例如:

    1. db.sample.employee.createIndex('idUniqueIdx', { 'id': 1 }, { 'Unique': true, 'Enforced': true })

    Note:

    在唯一索引包含多个字段时,只有每个字段均相同,才认为值是相同的。

  • 复合索引

    复合索引(多字段索引)是包含了一个以上字段的索引。如果匹配条件经常使用某几个字段,为它们创建复合索引,可以使准确查询更加高效。例如,sample.employee 集合中有 lastName 和 firstName 字段,为它们建立唯一索引。

    1. db.sample.employee.createIndex('nameIdx', { 'lastName': 1, 'firstName': 1 })

    假设业务有以下的查询:

    1. db.sample.employee.find({ 'lastName': 'Jafferson', 'firstName': 'John' })

    在使用复合索引时,该查询会比任意一个单字段的索引速度更快。

    复合索引会根据索引定义中字段的顺序排序。根据例子,nameIdx 会先根据 lastName 排序,在 lastName 相同时,再按 firstName 对 lastName 相同的记录排序。因此当查询条件只覆盖复合索引定义的前几个字段时,也能使用该索引地查询。例如,有定义为 { x: 1, y: 1, z: 1 } 的复合索引,那么以下的查询均可以使用复合索引。

    1. db.sample.employee.find({ 'x': 10, 'y': 10, 'z': 100 })
    2. db.sample.employee.find({ 'x': 10, 'y': 10 })
    3. db.sample.employee.find({ 'x': 10 })

    而类似 { 'y': 10 }{ 'y': 10, 'z': 100 } 的条件则无法使用索引。

  • 其它索引选项

    • NotNull:如果不允许索引字段不存在或者为 null,可以将这个选项设置为 true。
    • SortBufferSize:创建索引时使用的排序缓存的大小。在集合记录数据量较大时(大于1000万条记录)适当增大排序缓存大小可以提高创建索引的速度。

使用索引查询

一般地,SequoiaDB 会自动生成访问计划决定查询是否使用索引扫描,以及使用哪个索引去扫描。如果需要指定索引来进行查询,可以使用SdbQuery.hint()接口完成。如,在 sample.employee 集合上指定 idIdx 索引来查询 id 为 999 的记录。

  1. db.sample.employee.find({ 'id': 999 }).hint({ '': 'idIdx' })

如需查看索引使用情况,可以使用 SdbQuery.explain()。ScanType 字段为 ixscan 说明使用了索引,否则,ScanType 为 tbscan。在 IndexName 字段中可以查看所使用的是哪个索引。

  1. > db.sample.employee.find({ 'id': 999 }).explain()
  2. {
  3. "NodeName": "sdbserver:11740",
  4. "GroupName": "group1",
  5. "Role": "data",
  6. "Name": "sample.employee",
  7. "ScanType": "ixscan",
  8. "IndexName": "idIdx",
  9. "UseExtSort": false,
  10. "Query": {
  11. "$and": []
  12. },
  13. "IXBound": {
  14. "_id": [
  15. [
  16. {
  17. "$minElement": 1
  18. },
  19. {
  20. "$maxElement": 1
  21. }
  22. ]
  23. ]
  24. },
  25. "NeedMatch": false,
  26. "ReturnNum": 0,
  27. "ElapsedTime": 0.000052,
  28. "IndexRead": 0,
  29. "DataRead": 0,
  30. "UserCPU": 0,
  31. "SysCPU": 0
  32. }

访问计划使用索引的决策决定于集合的统计信息。分析集合和索引的数据,有助于生成更高效的访问计划。收集统计信息请使用Sdb.analyze()

删除索引

如需删除无用的索引,可以参考SdbCollection.dropIndex()接口。例如,删除集合 sample.employee 中名为 idIdx 的索引:

  1. db.sample.employee.dropIndex('idIdx')

基本原理

创建索引时,数据库会将指定字段的值拷贝到一个数据结构索引项中,并对其进行排序。使用索引查询时,数据库会从索引中找到满足条件的索引项,然后根据索引项中记录的位置信息,找到完整的记录。从而实现高效的查询。索引项是以 B 树的形式组织的,因此使用树的遍历可以快速地找到满足条件的索引项。

图 1 中,对集合中的 id 字段建立了索引。查询 id = 5 的记录。流程如红色线所示。

avatar

该查询分为以下几个步骤:

  • 找到 id 字段对应的索引
  • 在索引中,通过遍历 B 树的方式,找到符合条件的索引项
  • 通过索引项中记录的位置信息,找到完整的记录,并返回
  • 查询完成