MongoDB 入门教程实战学习笔记-15-mongo 查询优化点

小编 2026-07-01 阅读:1773 评论:0
性能优化 知道 index 和执行计划之后,我们要学会分析执行计划。选择出最优秀的索引方案。 索引准则 避免全表扫描 避免过多索引。如无必要,不增索引。 关注点 关注点1:执行时间...

性能优化

知道 index 和执行计划之后,我们要学会分析执行计划。选择出最优秀的索引方案。

索引准则

  1. 避免全表扫描

  2. 避免过多索引。如无必要,不增索引。

关注点

关注点1:执行时间

其中有3个executionTimeMillis,分别是

executionStats.executionTimeMillis

该query的整体查询时间

executionStats.executionStages.executionTimeMillis

该查询根据index去检索document获取2条具体数据的时间

executionStats.executionStages.inputStage.executionTimeMillis

该查询扫描 2 行index所用时间

这三个值我们都希望越少越好,那么是什么影响这这三个返回值呢?

抛开硬件因素等不谈,我们来进行下一层的剥离。

关注点2:返回的条数

这里主要谈3个返回项,nReturned,totalKeysExamined与totalDocsExamined,分别代表该条查询返回的条目、索引扫描条目和文档扫描条目。

很好理解,这些都直观的影响到executionTimeMillis,我们需要扫描的越少速度越快。

对于一个查询, 我们最理想的状态是

nReturned=totalKeysExamined & totalDocsExamined=0

(cover index,仅仅使用到了index,无需文档扫描,这是最理想状态。)

或者

nReturned=totalKeysExamined=totalDocsExamined

(需要具体情况具体分析)

(正常index利用,无多余index扫描与文档扫描。)

如果有sort的时候,为了使得sort不在内存中进行,我们可以在保证 nReturned=totalDocsExamined 的基础上,totalKeysExamined可以大于totalDocsExamined与nReturned,因为量级较大的时候内存排序非常消耗性能。

后面我们会针对例子来进行分析。

关注点3:Stage状态分析

那么又是什么影响到了totalKeysExamined与totalDocsExamined呢?

就是Stage的类型,Stage的具体含义在上文中有提及,如果认真看的同学就不难理解为何Stage会影响到totalKeysExamined 和totalDocsExamined从而影响executionTimeMillis了。

此前有讲解过stage的类型,这里不再赘述。

普通查询

  • 希望看到
Fetch+IDHACK

Fetch+ixscan

Limit+(Fetch+ixscan)

PROJECTION+ixscan

SHARDING_FILTER+ixscan
...
  • 不希望看到

COLLSCAN(全表扫),SORT(使用sort但是无index),不合理的SKIP,SUBPLA(未用到index的$or)

对于 count

  • 希望看到的有

COUNT_SCAN

  • 不希望看到的有

COUNTSCAN

Explain 实例

初始化测试数据

db.explain.insertMany([
   { \"_id\" : ObjectId(\"55b86d6bd7e3f4ccaaf20d70\"), \"a\" : 1, \"b\" : 1, \"c\" : 1 },
    { \"_id\" : ObjectId(\"55b86d6fd7e3f4ccaaf20d71\"), \"a\" : 1, \"b\" : 2, \"c\" : 2 },
    { \"_id\" : ObjectId(\"55b86d72d7e3f4ccaaf20d72\"), \"a\" : 1, \"b\" : 3, \"c\" : 3 },
    { \"_id\" : ObjectId(\"55b86d74d7e3f4ccaaf20d73\"), \"a\" : 4, \"b\" : 2, \"c\" : 3 },
    { \"_id\" : ObjectId(\"55b86d75d7e3f4ccaaf20d74\"), \"a\" : 4, \"b\" : 2, \"c\" : 5 },
    { \"_id\" : ObjectId(\"55b86d77d7e3f4ccaaf20d75\"), \"a\" : 4, \"b\" : 2, \"c\" : 5 },
    { \"_id\" : ObjectId(\"55b879b442bfd1a462bd8990\"), \"a\" : 2, \"b\" : 1, \"c\" : 1 },
    { \"_id\" : ObjectId(\"55b87fe842bfd1a462bd8991\"), \"a\" : 1, \"b\" : 9, \"c\" : 1 },
    { \"_id\" : ObjectId(\"55b87fe942bfd1a462bd8992\"), \"a\" : 1, \"b\" : 9, \"c\" : 1 },
    { \"_id\" : ObjectId(\"55b87fe942bfd1a462bd8993\"), \"a\" : 1, \"b\" : 9, \"c\" : 1 }
])

业务需求

查询 a=1,b 的值小于3,且按照 c 倒叙排序

db.d.find({a:1,b:{$lt:3}}).sort({c:-1})

没有索引的情况

db.explain.find({a:1,b:{$lt:3}}).sort({c:-1}).explain(\'executionStats\')

结果如下:

{
    \"queryPlanner\" : {
        \"plannerVersion\" : 1,
        \"namespace\" : \"test.explain\",
        \"indexFilterSet\" : false,
        \"parsedQuery\" : {
            \"$and\" : [ 
                {
                    \"a\" : {
                        \"$eq\" : 1.0
                    }
                }, 
                {
                    \"b\" : {
                        \"$lt\" : 3.0
                    }
                }
            ]
        },
        \"winningPlan\" : {
            \"stage\" : \"SORT\",
            \"sortPattern\" : {
                \"c\" : -1.0
            },
            \"inputStage\" : {
                \"stage\" : \"SORT_KEY_GENERATOR\",
                \"inputStage\" : {
                    \"stage\" : \"COLLSCAN\",
                    \"filter\" : {
                        \"$and\" : [ 
                            {
                                \"a\" : {
                                    \"$eq\" : 1.0
                                }
                            }, 
                            {
                                \"b\" : {
                                    \"$lt\" : 3.0
                                }
                            }
                        ]
                    },
                    \"direction\" : \"forward\"
                }
            }
        },
        \"rejectedPlans\" : []
    },
    \"executionStats\" : {
        \"executionSuccess\" : true,
        \"nReturned\" : 2,
        \"executionTimeMillis\" : 0,
        \"totalKeysExamined\" : 0,
        \"totalDocsExamined\" : 10,
        \"executionStages\" : {
            \"stage\" : \"SORT\",
            \"nReturned\" : 2,
            \"executionTimeMillisEstimate\" : 0,
            \"works\" : 16,
            \"advanced\" : 2,
            \"needTime\" : 13,
            \"needYield\" : 0,
            \"saveState\" : 0,
            \"restoreState\" : 0,
            \"isEOF\" : 1,
            \"invalidates\" : 0,
            \"sortPattern\" : {
                \"c\" : -1.0
            },
            \"memUsage\" : 126,
            \"memLimit\" : 33554432,
            \"inputStage\" : {
                \"stage\" : \"SORT_KEY_GENERATOR\",
                \"nReturned\" : 2,
                \"executionTimeMillisEstimate\" : 0,
                \"works\" : 13,
                \"advanced\" : 2,
                \"needTime\" : 10,
                \"needYield\" : 0,
                \"saveState\" : 0,
                \"restoreState\" : 0,
                \"isEOF\" : 1,
                \"invalidates\" : 0,
                \"inputStage\" : {
                    \"stage\" : \"COLLSCAN\",
                    \"filter\" : {
                        \"$and\" : [ 
                            {
                                \"a\" : {
                                    \"$eq\" : 1.0
                                }
                            }, 
                            {
                                \"b\" : {
                                    \"$lt\" : 3.0
                                }
                            }
                        ]
                    },
                    \"nReturned\" : 2,
                    \"executionTimeMillisEstimate\" : 0,
                    \"works\" : 12,
                    \"advanced\" : 2,
                    \"needTime\" : 9,
                    \"needYield\" : 0,
                    \"saveState\" : 0,
                    \"restoreState\" : 0,
                    \"isEOF\" : 1,
                    \"invalidates\" : 0,
                    \"direction\" : \"forward\",
                    \"docsExamined\" : 10
                }
            }
        }
    },
    \"serverInfo\" : {
        \"host\" : \"c120a2890d51\",
        \"port\" : 27017,
        \"version\" : \"4.0.4\",
        \"gitVersion\" : \"f288a3bdf201007f3693c58e140056adf8b04839\"
    },
    \"ok\" : 1.0
}

此处的条数较少,暂时不关心时间。

我们开始看条数和 executionStats。

\"nReturned\" : 2,        -- 符合的条件的返回为2条。
\"executionTimeMillis\" : 0,
\"totalKeysExamined\" : 0,    -- 未命中索引
\"totalDocsExamined\" : 10,   -- 扫描了所有记录
  • 排序

executionStats.executionStages.sort=SORT 没有命中索引

  • 内存占用
\"memUsage\" : 126,       -- 占用的内存
\"memLimit\" : 33554432,  -- 内存限制
  • 扫描类型

executionStages.inputStage.stage=COLLSCAN,全表扫描,扫描条件为

 \"filter\" : {
    \"$and\" : [ 
        {
            \"a\" : {
                \"$eq\" : 1.0
            }
        }, 
        {
            \"b\" : {
                \"$lt\" : 3.0
            }
        }
    ]
}

优化步骤

排序字段优化

排序添加索引

db.explain.createIndex({c: 1})

查看执行计划

\"executionStats\" : {
    \"executionSuccess\" : true,
    \"nReturned\" : 2,
    \"executionTimeMillis\" : 4,
    \"totalKeysExamined\" : 10,
    \"totalDocsExamined\" : 10,
    \"executionStages\" : {
        \"stage\" : \"FETCH\",
        \"filter\" : {
            \"$and\" : [ 
                {
                    \"a\" : {
                        \"$eq\" : 1.0
                    }
                }, 
                {
                    \"b\" : {
                        \"$lt\" : 3.0
                    }
                }
            ]
        },
        \"nReturned\" : 2,
        \"executionTimeMillisEstimate\" : 0,
        \"works\" : 11,
        \"advanced\" : 2,
        \"needTime\" : 8,
        \"needYield\" : 0,
        \"saveState\" : 0,
        \"restoreState\" : 0,
        \"isEOF\" : 1,
        \"invalidates\" : 0,
        \"docsExamined\" : 10,
        \"alreadyHasObj\" : 0,
        \"inputStage\" : {
            \"stage\" : \"IXSCAN\",
            \"nReturned\" : 10,
            \"executionTimeMillisEstimate\" : 0,
            \"works\" : 11,
            \"advanced\" : 10,
            \"needTime\" : 0,
            \"needYield\" : 0,
            \"saveState\" : 0,
            \"restoreState\" : 0,
            \"isEOF\" : 1,
            \"invalidates\" : 0,
            \"keyPattern\" : {
                \"c\" : 1.0
            },
            \"indexName\" : \"c_1\",
            \"isMultiKey\" : false,
            \"multiKeyPaths\" : {
                \"c\" : []
            },
            \"isUnique\" : false,
            \"isSparse\" : false,
            \"isPartial\" : false,
            \"indexVersion\" : 2,
            \"direction\" : \"backward\",
            \"indexBounds\" : {
                \"c\" : [ 
                    \"[MaxKey, MinKey]\"
                ]
            },
            \"keysExamined\" : 10,
            \"seeks\" : 1,
            \"dupsTested\" : 0,
            \"dupsDropped\" : 0,
            \"seenInvalidated\" : 0
        }
    }
}

结果分析

  • 排序

Stage没有了SORT,因为我们sort字段有了index,但是由于查询还是没有index,故totalDocsExamined还是10,但是由于sort用了index,totalKeysExamined也是10

  • 查询

还是扫描了所有的数据,需要进一步优化。

\"nReturned\" : 2,
\"executionTimeMillis\" : 4,
\"totalKeysExamined\" : 10,
\"totalDocsExamined\" : 10,

查询优化

新建索引

删除原来的索引。

db.explain.dropIndexes();
db.explain.createIndex({a:1, b:1, c:1});

执行计划

\"executionStats\" : {
    \"executionSuccess\" : true,
    \"nReturned\" : 2,
    \"executionTimeMillis\" : 6,
    \"totalKeysExamined\" : 2,
    \"totalDocsExamined\" : 2,
    \"executionStages\" : {
        \"stage\" : \"SORT\",
        \"nReturned\" : 2,
        \"executionTimeMillisEstimate\" : 0,
        \"works\" : 7,
        \"advanced\" : 2,
        \"needTime\" : 4,
        \"needYield\" : 0,
        \"saveState\" : 0,
        \"restoreState\" : 0,
        \"isEOF\" : 1,
        \"invalidates\" : 0,
        \"sortPattern\" : {
            \"c\" : -1.0
        },
        \"memUsage\" : 126,
        \"memLimit\" : 33554432,
        \"inputStage\" : {
            \"stage\" : \"SORT_KEY_GENERATOR\",
            \"nReturned\" : 2,
            \"executionTimeMillisEstimate\" : 0,
            \"works\" : 4,
            \"advanced\" : 2,
            \"needTime\" : 1,
            \"needYield\" : 0,
            \"saveState\" : 0,
            \"restoreState\" : 0,
            \"isEOF\" : 1,
            \"invalidates\" : 0,
            \"inputStage\" : {
                \"stage\" : \"FETCH\",
                \"nReturned\" : 2,
                \"executionTimeMillisEstimate\" : 0,
                \"works\" : 3,
                \"advanced\" : 2,
                \"needTime\" : 0,
                \"needYield\" : 0,
                \"saveState\" : 0,
                \"restoreState\" : 0,
                \"isEOF\" : 1,
                \"invalidates\" : 0,
                \"docsExamined\" : 2,
                \"alreadyHasObj\" : 0,
                \"inputStage\" : {
                    \"stage\" : \"IXSCAN\",
                    \"nReturned\" : 2,
                    \"executionTimeMillisEstimate\" : 0,
                    \"works\" : 3,
                    \"advanced\" : 2,
                    \"needTime\" : 0,
                    \"needYield\" : 0,
                    \"saveState\" : 0,
                    \"restoreState\" : 0,
                    \"isEOF\" : 1,
                    \"invalidates\" : 0,
                    \"keyPattern\" : {
                        \"a\" : 1.0,
                        \"b\" : 1.0,
                        \"c\" : 1.0
                    },
                    \"indexName\" : \"a_1_b_1_c_1\",
                    \"isMultiKey\" : false,
                    \"multiKeyPaths\" : {
                        \"a\" : [],
                        \"b\" : [],
                        \"c\" : []
                    },
                    \"isUnique\" : false,
                    \"isSparse\" : false,
                    \"isPartial\" : false,
                    \"indexVersion\" : 2,
                    \"direction\" : \"forward\",
                    \"indexBounds\" : {
                        \"a\" : [ 
                            \"[1.0, 1.0]\"
                        ],
                        \"b\" : [ 
                            \"[-inf.0, 3.0)\"
                        ],
                        \"c\" : [ 
                            \"[MinKey, MaxKey]\"
                        ]
                    },
                    \"keysExamined\" : 2,
                    \"seeks\" : 1,
                    \"dupsTested\" : 0,
                    \"dupsDropped\" : 0,
                    \"seenInvalidated\" : 0
                }
            }
        }
    }
}

结果分析

\"executionStats\" : {
 \"executionSuccess\" : true,
 \"nReturned\" : 2,
 \"executionTimeMillis\" : 6,
 \"totalKeysExamined\" : 2,
 \"totalDocsExamined\" : 2,
 \"executionStages\" : {
     \"stage\" : \"SORT\",
  • 查询

查询已经满足我们的条件了。

  • 排序

但是排序为 SORT,属于内存排序,不符合我们的预期。

sort 优化方式1

新建索引

删除原来的索引。

db.explain.dropIndexes();
db.explain.createIndex({a:1, c:1, b:1});

执行计划

\"executionStats\" : {
    \"executionSuccess\" : true,
    \"nReturned\" : 2,
    \"executionTimeMillis\" : 5,
    \"totalKeysExamined\" : 4,
    \"totalDocsExamined\" : 2,
    \"executionStages\" : {
        \"stage\" : \"FETCH\",
        \"nReturned\" : 2,
        \"executionTimeMillisEstimate\" : 0,
        \"works\" : 5,
        \"advanced\" : 2,
        \"needTime\" : 2,
        \"needYield\" : 0,
        \"saveState\" : 0,
        \"restoreState\" : 0,
        \"isEOF\" : 1,
        \"invalidates\" : 0,
        \"docsExamined\" : 2,
        \"alreadyHasObj\" : 0,
        \"inputStage\" : {
            \"stage\" : \"IXSCAN\",
            \"nReturned\" : 2,
            \"executionTimeMillisEstimate\" : 0,
            \"works\" : 5,
            \"advanced\" : 2,
            \"needTime\" : 2,
            \"needYield\" : 0,
            \"saveState\" : 0,
            \"restoreState\" : 0,
            \"isEOF\" : 1,
            \"invalidates\" : 0,
            \"keyPattern\" : {
                \"a\" : 1.0,
                \"c\" : 1.0,
                \"b\" : 1.0
            },
            \"indexName\" : \"a_1_c_1_b_1\",
            \"isMultiKey\" : false,
            \"multiKeyPaths\" : {
                \"a\" : [],
                \"c\" : [],
                \"b\" : []
            },
            \"isUnique\" : false,
            \"isSparse\" : false,
            \"isPartial\" : false,
            \"indexVersion\" : 2,
            \"direction\" : \"backward\",
            \"indexBounds\" : {
                \"a\" : [ 
                    \"[1.0, 1.0]\"
                ],
                \"c\" : [ 
                    \"[MaxKey, MinKey]\"
                ],
                \"b\" : [ 
                    \"(3.0, -inf.0]\"
                ]
            },
            \"keysExamined\" : 4<															
版权声明

本文仅代表作者观点,不代表百度立场。
本文系作者授权百度百家发表,未经许可,不得转载。

热门文章
  • Sequential Monte Carlo Methods (SMC) 序列蒙特卡洛/粒子滤波/Bootstrap Filtering

    Sequential Monte Carlo Methods (SMC) 序列蒙特卡洛/粒子滤波/Bootstrap Filtering
    Problem Statement 我们考虑一个具有马尔可夫性质、非线性、非高斯的状态空间模型(State Space Model):对于一个时间序列上的观测结果{yt,t∈N}\\{ y_t , t \\in N \\}{yt​,t∈N},我们认为每个观测结果yty_tyt​的生成依赖于一个无法直接观察的隐变量xt∈{xt,t∈N}x_t \\in \\{x_t , t \\in N \\}xt​∈{xt​,t∈N},即:p(...
  • 机房智能化温湿度解决方式之POE供电以太网温湿度传感器

    机房智能化温湿度解决方式之POE供电以太网温湿度传感器
    机房智能化温湿度解决方式之POE供电以太网温湿度传感器 北京盈创力和电子科技有限公司 智能型TCP网口温湿度记录仪 北京IP网络温湿度记录仪厂家,北京盈创力和 北京智能型TCP网口温湿度记录仪IP网络温湿度记录仪是一种新型的基于TCP/IP协议双绞线以太网标准温湿度采集模块,利用它可以实现现场温度值、相对湿度值的采集,同时利用其自身的RJ45通信接口可以方便地和机房监控主机或交换机集线器进行联网。 工作于-40℃~85℃工业级带...
  • HTTP状态保持的原理

    HTTP状态保持的原理
    a)在用户登录之后,浏览器返回响应的时候会在响应中添加上cookieb)浏览器接收到cookie之后会自动保存c)当用户再次请求同一服务器中的其他网页的时候,浏览器会自动带上之前保存的cookied)服务接收到请求之后可以请 request 对象中取到cookie 判断当前用户是否登录  Http是无状态的,就是连接时数据互通,关闭后...
  • Hive 系统函数及示例

    Hive 系统函数及示例
    查看所有系统函数 show functions; 函数分类 内置函数【系统函数】 数学函数: floor、round、ceil、cos、log2等 字符串函数: length、reverse、trim、lower、get_json_object、repeat等 收集函数: size 转换函数: cast 日期函数: year、month、datediff、date、date_add等 条件函数: coalesce、case…w...
  • CSRF的原理和防范措施

    CSRF的原理和防范措施
    a)攻击原理:i.用户C访问正常网站A时进行登录,浏览器保存A的cookieii.用户C再访问攻击网站B,网站B上有某个隐藏的链接或者图片标签会自动请求网站A的URL地址,例如表单提交,传指定的参数iii.而攻击网站B在访问网站A的时候,浏览器会自动带上网站A的cookieiv.所以网站A在接收到请求之后可判断当前用户是登录状态,所以...
标签列表