优化MongoDB6.0/7.0慢查询:用db.setProfilingLevel()捕获慢操作,explain("executionStats")分析索引,$indexStats清理冗余索引;联合索引字段顺序需遵循等值→范围→排序原则,并定期监控与调整。
### 第一步:使用 `db.setProfilingLevel()` 捕获慢查询
Profile 功能是定位“哪条查询慢、为什么慢”的唯一入口。但默认处于关闭状态,需要手动启用。级别选择不当,等于无效配置。
* **推荐方案**:执行 `db.setProfilingLevel(1, { slowms: 50 })`。该设置仅记录执行时间超过50毫秒的操作,并写入 `system.profile` 集合,对线上业务影响可控。
* **禁止在生产环境**使用 `db.setProfilingLevel(2)`。此选项会记录所有操作,导致 `system.profile` 集合(默认容量仅为1MB)迅速被写满,并使写入性能明显下降。
* **如需永久生效**,可在配置文件中设置:`operationProfiling.mode: "slowOp"` 和 `operationProfiling.slowOpThresholdMs: 50`,注意不要写成 `slowms`。
* **设置后务必验证**:执行 `db.getProfilingStatus()`,若返回 `{ "was": 1, "slowms": 50, ... }`,则表示配置已生效。
### 第二步:通过 `explain("executionStats")` 分析慢查询根源
仅查看 `system.profile` 中的 `millis` 和 `ns` 字段并不足够——它不会告知具体使用了哪个索引以及扫描了多少文档。必须对慢查询语句追加 `explain("executionStats")` 进行深度排查。
* **首要关注点**:`executionStats.executionStages.stage`。若出现 `"COLLSCAN"`,表示查询未使用索引,这是最核心的问题。
* **对比关键指标**:`executionStats.nReturned` 与 `executionStats.totalDocsExamined`。如果后者远大于前者(例如扫描10万条文档仅返回10条),通常意味着索引未能覆盖查询字段,或字段顺序不合理。
* **检查索引匹配情况**:查看 `executionStats.executionStages.inputStage.keyPattern`,确认命中的索引是否与预期一致。例如,避免让 `company_id_hashed` 哈希索引替代组合索引,因为哈希索引对范围查询完全无效。
* **谨慎使用** `explain("allPlansExecution")`:该功能在6.0版本之后可用,但会真实执行所有候选计划,可能导致锁表或增加负载,线上环境应避免滥用。
### 第三步:借助 `$indexStats` 清理无用索引
索引数量过多而不清理,会持续降低写入性能。在6.0和7.0中,使用 `$indexStats` 聚合阶段可以直观地查看索引的实际使用频率。
* **执行命令**:`db.collection.aggregate([ { $indexStats: {} }, { $sort: { "accesses.ops": 1 } } ])`,按访问次数升序排列,最底部的索引即为“僵尸索引”。
* **解读 `accesses.ops`**:该字段自MongoDB 3.2版本起统计索引命中次数。若某索引的 `accesses.ops` 为0且存在超过半年,通常可以安全删除。
* **检查重复索引**:注意 `keyPattern` 是否重复。例如,已有 `{a:1,b:1}` 索引,又创建了 `{a:1,b:1,c:1}`,则前者很可能冗余。
* **删除前确认**:先执行 `db.collection.getIndexes()` 查看所有索引,并确认应用层是否通过 `hint()` 硬编码依赖该索引名称,避免导致线上故障。
### 第四步:合理设计联合索引的字段顺序
许多慢查询的根源不在于是否建立索引,而在于索引字段的排列顺序。MongoDB 6.0和7.0的查询优化器仍严格遵循“等值 → 范围 → 排序”的原则。
* **示例查询**:若条件为 `{ company_id: 13272, create_time: { $gte: ..., $lte: ... } }`,则索引必须为 `{ company_id: 1, create_time: 1 }`。顺序颠倒会导致索引失效。
* **包含排序时**:若查询还包含 `sort({ status: 1 })`,理想索引为 `{ company_id: 1, create_time: 1, status: 1 }`,将排序字段置于最后。
* **哈希索引限制**:哈希索引(`"hashed"`)仅适用于等值查询,对 `$gte`、`$in` 等范围查询无效。不要将哈希字段与范围字段混用构建联合索引。
* **建索引后验证**:立即执行 `db.collection.find(...).explain("executionStats")`,确认 `stage` 变为 `"IXSCAN"`,且 `totalDocsExamined` 接近 `nReturned`,才算完成优化。
真正导致效率瓶颈的,往往不是命令本身,而是开启了 Profile 却未查询 `system.profile`,或者查看了 `explain` 却忽略了 `totalDocsExamined` 与 `keyPattern` 的匹配关系。索引并非越多越好,而是要让每条慢查询都能精准地落在一个“最小必要”的索引上。
侠游戏发布此文仅为了传递信息,不代表侠游戏网站认同其观点或证实其描述