一、问题:数据量涨了,查询变慢了

简记往来上线初期,用户少、数据少,所有查询都是毫秒级返回。

随着用户增长到6.8万,记录数突破62万条,一些复杂的聚合查询开始变慢——最慢的到了600ms。

600ms对用户来说就是“卡住了”。必须优化。

二、索引的基本原理

MongoDB的索引和MySQL的索引原理类似:索引是一种数据结构(B-Tree),让数据库不必扫描全表就能找到数据。

没有索引时,查询 WHERE book_id = ? AND contact_id = ? 需要扫描所有记录(62万条)。有索引后,只需要扫描索引中匹配的少量记录。

索引设计的原则

  1. 为查询建索引:不是所有字段都需要索引,只为WHERE和ORDER BY涉及的字段建
  2. 复合索引的顺序很重要:最常用的查询条件放在前面
  3. 选择性高的字段优先:值分布广的字段(如contact_id)比值分布窄的字段(如type)更适合建索引

三、简记往来的核心索引

根据查询模式,简记往来建立了三个核心索引:

// 1. 按账本+日期查询(流水页)
db.records.createIndex({ book_id: 1, date: -1 })

// 2. 按账本+联系人查询(差额统计)——最常用
db.records.createIndex({ book_id: 1, contact_id: 1 })

// 3. 按账本+类型统计(收/送礼汇总)
db.records.createIndex({ book_id: 1, type: 1 })

复合索引遵循 ESR 规则:Equality(等值条件)→ Sort(排序字段)→ Range(范围条件)。

四、索引对查询性能的影响

查询类型 无索引 有索引 提升
按账本查询流水 全表扫描(62万条) 索引扫描(几百条) 100x+
按账本+联系人查询 全表扫描 索引精准定位 1000x+
差额统计聚合 全表扫描+内存聚合 索引扫描+索引聚合 100x+

没有索引时,一次简单的查询可能要扫描几十万条记录。有索引后,只需要扫描几条或几十条。

五、如何判断索引是否生效?

使用 MongoDB 的 explain() 方法分析查询执行计划:

db.records.find({ book_id: 'book_xxx', contact_id: 'contact_xxx' }).explain('executionStats')

关键指标:

  • totalDocsExamined:扫描的文档数(越少越好)
  • totalKeysExamined:扫描的索引键数
  • executionTimeMillis:执行时间

如果 totalDocsExamined 远大于 totalKeysExamined,说明索引没生效,或者索引设计不合理。

六、索引维护策略

索引不是建完就不管了。定期维护:

  1. 监控索引使用率:MongoDB的 $indexStats 可以查看每个索引的使用频率
  2. 删除未使用的索引:节省存储空间,提升写入性能
  3. 重建碎片化索引:数据大量变更后,索引可能碎片化,需要重建
// 查看索引使用统计
db.records.aggregate([{ $indexStats: {} }])

七、总结

索引优化的核心是:根据查询模式设计索引,而不是给所有字段建索引。

简记往来当前62万条记录,核心查询响应时间稳定在150ms以内。这个性能是靠三个精心设计的复合索引支撑的。

下一篇,我们来聊聊慢查询优化实录——从600ms到80ms。

评论区聊聊:你的数据库查询遇到过慢查询吗?怎么解决的?

更多推荐