LiteDB 使用文档字段索引来改善搜索性能。每个索引存储着指定字段的值,并按字段的值 (和类型) 排序。如果没有索引,LiteDB 必须使用全文档扫描来执行一个查询。全文档扫描是毫无效率的,因为 LiteDB 必须反序列化所有文档并一个一个测试(查询条件)。

索引实现

LiteDB 使用了一个简单的索引方案:跳跃列表。跳跃列表是有序的双向链表,链接可以达到 32 级。跳跃列表非常容易实现 (只要 15 行代码) 并且统计均衡,测试结果很不错:插入和查找结果平均复杂度 O(ln n) = 1 百万文档 = 13 步。如果你想了解更多关于跳跃列表的信息,请查看这个牛X的视频

文档是无模式的,即使它们在同一个集合里。因此,你可以在任何一个字段上创建索引,这个字段可以在一个文档中是这种类型,在另外一个文档中是另一种类型。当同一个字段数据类型不同时,LiteDB 只比较类型。每个类型都有一个次序:

BSON 类型 次序
MinValue 1
Null 2
Int32, Int64, Double, Decimal 3
String 4
Document 5
Array 6
Binary 7
ObjectId 8
Guid 9
Boolean 10
DateTime 11
MaxValue 12
  • 数字 (Int32, Int64, Double 或 Decimal) 都是同样的次序。如果你在同一个文档字段上混合使用这些数字类型,当比较时,LiteDB 会将它们转换为 Decimal

EnsureIndex()

索引通过 EnsureIndex 创建。这个实例方法确认一个索引:如果不存在就创建索引,如果已存在就什么也不做。在 v4 中,改变定义不会再重新创建索引。要想重新创建一个索引,你首先要删除此索引,然后再执行 EnsureIndex

索引用文档字段名称标识。LiteDB 只支持一个索引一个字段,但是这个字段可以是任何 BSON 类型,甚至是一个嵌入的文档。

  1. {
  2. _id: 1,
  3. Address:
  4. {
  5. Street: "Av. Protasio Alves, 1331",
  6. City: "Porto Alegre",
  7. Country: "Brazil"
  8. }
  9. }
  • 你可以使用 EnsureIndex("Address") 为所有 Address 嵌入文档创建一个索引
  • 或者使用点分表示法 EnsureIndex("Address.Street")Street 上创建一个索引
  • 索引按 BsonDocument 字段执行。如果你在属性上使用了自定义的 ResolvePropertyName[BsonField] 特性,那就必须通过文档字段名称而不是属性名称来使用索引。参见对象映射.
  • 你可以通过 lambda 表达式在一个强类型集合中定义索引字段:EnsureIndex(x => x.Name)

多键值索引

当你在一个数组类型字段上创建索引时,所有的数组值都被包含在索引键中,这样可以搜索任何一个值。

  1. public class Customer
  2. {
  3. public int Id { get; set; }
  4. public string Name { get; set; }
  5. public string[] Phones { get; set; }
  6. }
  7. var customers = db.GetCollection<Customer>("customers");
  8. customers.Insert(new Customer { Name = "John", Phones = new string[] { "1", "2", "5" });
  9. customers.Insert(new Customer { Name = "Doe", Phones = new string[] { "1", "8" });
  10. customers.EnsureIndex(x => x.Phones, "$.Phones[*]");
  11. var result = customers.Query(x => x.Phones.Contains("1")); // returns both documents

表达式

在 v4 中,通过支持多键值执行可以创建一个基于表达式的索引。这样,你可以索引那些不直接是字段值的任何重要信息,像这样:

  • db.EnsureIndex("customer", "Name", false, "LOWER($.Name)")
  • db.EnsureIndex("customer", "Total", false, "SUM($.Items[*].Price)")
  • db.EnsureIndex("customer", "CheapBooks", false, "LOWER($.Books[@.Price < 20].Title)")

查看表达式 获取关于表达式的更多细节。

v4 中的变化

  • 不再有自动的索引创建,你必须在数据库初始化时运行 EnsureIndex
  • 如果你尝试执行没有索引的查询,查询会通过全搜索进行
  • 如果你使用没有解析到 Query 的 LINQ 表达式来查询,查询引擎会在映射到对象后才执行查询
  • 如果你的查询有一个 And 操作,查询引擎只会在其中一个(子查询)中使用索引 (如果存在),而其他会使用全扫描。这会优化结果,防止多索引查询。总是尝试使用左侧那个。
  1. col.EnsureIndex(x => x.Name);
  2. col.EnsureIndex(x= > x.Age);
  3. var r = col.Find(x => x.Name == "John" && x.Age > 20 && x.Phones.Length > 1);

在这个例子中,LiteDB 使用 Name 索引来获得第一个结果。对于 Age > 20 来说,需要对所有 Name == 'John' 的文档使用全扫描。然后,同理于 Phones.Length > 1

限制

  • 索引值必须少于 512 字节 (序列化为 BSON 后)
  • 每个集合最多 16 个索引 - 包括 _id 主键