TinyDb 索引链路由 3 层组成:
IndexScanner:扫描实体特性并自动建索引。IndexManager:集合级索引生命周期管理。BTreeIndex / DiskBTree:底层 B+ 树存储与检索。
1. 自动索引从哪里来
DocumentCollection<T> 构造时会调用:
CreateAutoIndexes();内部就是 IndexScanner.ScanAndCreateIndexes(engine, typeof(T), collectionName),默认会做:
创建主键索引(
_id,唯一)。扫描属性上的
[Index]。扫描类上的
[CompositeIndex]。
2. 特性如何映射到索引
[Entity("users")]
[CompositeIndex("idx_age_city", "Age", "City", Unique = false)]
public partial class User
{
[Id]
public ObjectId Id { get; set; } = ObjectId.NewObjectId();
[Index(Unique = true)]
public string Email { get; set; } = string.Empty;
[Index]
public int Age { get; set; }
[Index]
public string City { get; set; } = string.Empty;
}注意:字段名会经过 camelCase 兼容转换,避免序列化字段名不一致。
3. IndexManager 的职责
IndexManager 主要负责:
CreateIndex / DropIndexIndexExists / GetIndexGetBestIndex(给优化器选索引)在文档变更时同步索引:
InsertDocument(oldDoc)UpdateDocument(oldDoc, newDoc)DeleteDocument(doc)
更新时它会先删旧键再插新键;若唯一索引冲突,会尝试回滚旧键,保证索引一致性。
4. BTreeIndex 的核心能力
BTreeIndex 提供:
Insert/ DeleteFindExactFindRange / FindRangeReverseGetAll / GetAllReverseValidate / GetStatistics
并通过读写锁保证并发安全。
5. 唯一索引冲突处理
当 IsUnique=true 且键已存在,Insert 返回失败并在上层抛异常。
业务侧应把它当“约束失败”处理:
try
{
users.Insert(new User { Email = "dup@example.com" });
}
catch (InvalidOperationException ex)
{
// 唯一约束冲突
Console.WriteLine(ex.Message);
}6. 查询如何利用索引
QueryOptimizer 会根据条件字段和索引统计选择策略:
主键等值 ->
PrimaryKeyLookup唯一索引全等值 ->
IndexSeek普通索引或范围 ->
IndexScan无可用索引 ->
FullTableScan
所以“索引是否存在”决定了执行计划是否降级为全表扫描。
7. 索引设计建议(按优先级)
主键外,先建唯一业务键(如
Email、OrderNo)。高频过滤字段建立单列索引。
高频组合查询建立复合索引,字段顺序按过滤前缀排序。
不要盲目给所有字段建索引,写入成本会增加。
8. 常见陷阱
复合索引顺序错:命中率下降。
更新高频字段都在索引里:写放大明显。
小表过度索引:收益不大,复杂度增加。
9. 小结
TinyDb 索引系统的关键价值在于:
自动扫描特性降低接入成本。
IndexManager集中维护索引一致性。B+ 树支持等值、范围、顺序遍历,能覆盖大多数业务查询。
下一篇进入 AOT 与源码生成,解释为什么 [Entity] 是核心约束。
发表评论