在相同业务场景下,架构设计和库、集合、索引等设计会影响查询性能,良好的设计可以提高查询性能,反之会出现很多慢请求(执行时间很长的语句),这些慢请求会消耗很多的系统性能。
排查思路
DDS支持在控制台上查看慢日志信息,针对慢日志中过长的操作需要从执行最慢的操作入手,逐个进行优化。
- 对于操作执行时长超过1s时,对应的操作可能会有问题,需要视具体情况分析。
- 对于操作执行时长超过10s时,则需要优化对应的操作。
说明若存在某个聚合操作,因其比较慢,此时操作执行时长超过10s的情况属于正常现象。
分析方法
步骤 1 连接数据库。
步骤 2 针对慢查询可以通过如下命令检查其执行计划。
explain()
例如:
db.test.find({"data_id" : "ae4b5769-896f-465c-9fbd-3fd2f3357637"}).explain();
db.test.find({"data_id" : "775f57c2-b63e-45d7-b581-3822dba231b4"}).explain("executionStats");
对于查询而言,因为覆盖查询不需要读取文档,而是直接从索引中返回结果,这样的查询非常高效,所以 尽可能使用索引覆盖查询 。如果explain()的输出显示indexOnly字段为真,则说明这个查询就被一个索引覆盖。
步骤 3 执行计划解析。
- 查看执行时间。
executionStats.executionStages.executionTimeMillisEstimate和executionStats.executionStages.inputStage. executionTimeMillisEstimate时间越短越好。
参数说明
参数名称 | 描述 |
---|---|
executionStats.executionTimeMillis | 执行计划选择和执行的所有时间 |
executionStats.executionStages.executionTimeMillisEstimate | 最优执行计划的执行完成时间 |
executionStats.executionStages.inputStage. executionTimeMillisEstimate | 最优执行计划下的子阶段执行完成时间 |
- 查看扫描条数。
如下表中三个条目相同为最佳。
参数说明
参数名称 | 描述 |
---|---|
executionStats. nReturned | 匹配查询条件的文档数 |
executionStats .totalKeysExamined | 索引扫描条目数 |
executionStats .totalDocsExamined | 文档扫描条目数 |
- 查看Stage状态。
性能较好的Stage状态组合如下:
- Fetch+IDHACK
- Fetch+ixscan,
- Limit+(Fetch+ixscan)
- PROJECTION+ixscan
状态说明
状态名称 | 描述 |
---|---|
COLLSCAN | 全表扫描 |
SORT | 内存中进行排序 |
IDHACK | 根据_id进行查询 |
TEXT | 全文索引 |
COUNTSCAN | 未用索引计数 |
FETCH | 索引扫描 |
LIMIT | 使用Limit限制返回数 |
SUBPLA | 未用索引的$or查询阶段 |
PROJECTION | 使用索引计数 |
COUNT_SCAN | 使用索引计数 |
优化方案
- 对于无索引覆盖的查询,则需要根据查询条件创建对应索引。
- 对于点查可以创建hash索引。
- 对于多字段查询,单字段重复度较高的场景,创建复合索引。
- 对于范围查找,结果集有序的查询,创建升序或者降序索引。
- 对于复合索引,由于复合索引是前缀排序查询,所以查询条件的顺序要与索引字段的顺序一致。
- 对于分区集合(表)、大集合(超过10万记录),不要直接对大数据量的表使用模糊查询(即不能使用like)。这样会造成查询扫描的记录过多,建议先基于索引字段查询,过滤出较小的数据集后再使用模糊查询。
- 避免使用not。MongoDB 并不会对缺失的数据进行索引,因此not的查询条件将会要求在一个结果集中扫描所有记录。如果$not是唯一的查询条件,会对集合进行全表扫描。
- 用and时把匹配最少结果的条件放在最前面,用or时把匹配最多结果的条件放在最前面。
- 检查实例规格的性能基线,分析当前的业务需求是否达到上限,如已达当前实例性能瓶颈,请及时提升实例规格。