TT Lab
博客 开始
学习 学习路径 课程

MongoDB — 文档数据库的判断

按顺序和范围设计索引

在 TT Lab 中继续学习

一句话总结

索引的问题不是“建在哪个字段上”,而是**“以什么顺序、装进什么”**。同样的三个字段,顺序不同, 扫描的键数可能相差数千倍;装入的范围(部分索引、TTL)不同,大小和寿命也不同。判断要依据执行计划中的数字。

为什么需要——明明有索引却很慢

“建了索引为什么还慢”的报告,大多数不是没有索引,而是索引不匹配。来看列表页面上 一个常见的查询。

db.orders.find({status: "shipped", amount: {$gte: 150000}}).sort({createdAt: -1}).limit(20)

如果按查询中出现的顺序建立 {status, amount, createdAt} 索引,就会先用 status 缩小范围,再扫描 amount 的范围。但是范围 扫描出的结果并不是按 createdAt 排序的。于是执行计划中出现 SORT 阶段,把符合条件的数千条记录全部载入内存 排序之后再截取 20 条。limit 20 毫无帮助。

它如何工作——ESR

复合索引是从第一个字段开始按值排序的一张列表。由此得出顺序规则。

顺序 字段 原因
E 相等(status: "shipped") 缩小到一个区间。区间内部按下一个字段排序
S 排序(createdAt: -1) 在相等区间内按已排好的顺序遍历,排序就是免费的
R 范围(amount >= …) 一边遍历一边直接用键过滤。凑满 20 条就停止

如果建成 {status, createdAt, amount},执行计划会变成 LIMIT>FETCH>IXSCAN,扫描的键数会减少到刚好凑满 20 条的程度。 放在范围字段之后的字段只用于过滤,而不用于查找——这就是 ERS 与 ESR 的全部区别。

还有三点。

阅读执行计划的顺序

执行计划是一棵树。最外层(winningPlan)是最后做的事,顺着 inputStage 往下走,就会看到最先做的事。 LIMIT>FETCH>IXSCAN 是“扫描索引 → 取出文档 → 在 20 条处截断”,SORT>COLLSCAN 是“全部扫描 → 在内存中 排序”。阅读时要把三个数字放在一起看。

比较 含义
totalKeysExamined ≫ nReturned 用了索引但区间太宽——怀疑顺序或正则表达式
totalDocsExamined ≫ nReturned 取出文档后再过滤——用于过滤的字段不在索引中
totalDocsExamined = 0 只靠索引就给出了答案(覆盖)

规划器会让候选计划各自短暂地并行试跑,选出先返回较多结果的那个,并按查询形状缓存这个选择。落选的计划 留在 rejectedPlans 中。因此新建索引会清空缓存,同一个查询的计划可能改变——这就是修改之后要重新 测量的原因。如果想单独测量某个索引的表现,就用 hint 强制使用该索引;想在没有索引的情况下测量,就传入 {$natural: 1}。

在现场会遇到的情形

下一个实验要做的事

在 60,000 条订单上,把同一个列表查询分别在无索引、ERS、ESR 三种情况下测量并比较数字,然后依次测量覆盖查询、部分索引、TTL、正则 前缀。最后处理三个页面变慢的事件,区分同一症状背后各不相同的原因。