您当前的位置:首页 > 电脑百科 > 数据库 > 百科

线上MongoDB查询慢,如何通过索引优化直降响应时间?

时间:2024-04-29 12:26:25  来源:51CTO  作者:

作者 | 吴守阳

审校 | 重楼

背景

线上某个页面的响应速度异常缓慢,达到了16秒,严重影响了业务的正常运行。经过与研发的沟通得知,该页面调用的数据集合只会保留7天的数据,集合有6000万条记录。针对过期数据的处理,使用了根据 create_time 字段创建的过期索引,以自动使数据失效。此外,数据集合还通过 company_id 字段进行了哈希分片。

问题排查

慢语句分析

在后台拿到了慢查询语句,如下:

db.visitor.find({
 "company_id": 13272,
 "create_time": {
 "$gte": ISODate("2024-04-11T00:00:00.000+0800"),
 "$lte": ISODate("2024-04-11T23:59:59.000+0800")
 }
});

db.visitor.find({
 "company_id": 13272,
 "create_time": {
 "$gte": ISODate("2024-04-12T00:00:00.000+0800"),
 "$lte": ISODate("2024-04-18T23:59:59.000+0800")
 }
});

很简单的一个查询,语句上没有再优化的必要了。如果索引都在不应该出现这种十多秒的耗时,接下来开始分析索引。

索引分析

索引如下:

db.getCollection("visitor").createIndex({
 "company_id": "hashed"
}, {
 name: "company_id_hashed"
});

db.getCollection("visitor").createIndex({
 "company_id": NumberInt("1")
}, {
 name: "company_id_1"
});

db.getCollection("visitor").createIndex({
 "create_time": NumberInt("1")
}, {
 name: "create_time_1",
 expireAfterSeconds: NumberInt("604800")
});

其中:

  • company_id_hashed:创建集合分片使用的hash索引
  • company_id_1:普通查询的索引
  • create_time_1:过期时间的索引
    就这点数据量,按理说会用到索引的,不应该执行耗时16s,接下来执行计划分析。

ExplAIn执行计划

winningPlan

"stage": "SHARDING_FILTER",
 "inputStage": {
 "stage": "FETCH",
 "filter": {
 "$and": [
 {
 "company_id": {
 "$eq": 13272
 }
 },
 {
 "create_time": {
 "$lte": ISODate("2024-04-17T15:59:59.000Z")
 }
 },
 {
 "create_time": {
 "$gte": ISODate("2024-04-10T16:00:00.000Z")
 }
 }
 ]
 },
 "inputStage": {
 "stage": "IXSCAN",
 "keyPattern": {
 "company_id": "hashed"
 },
 "indexName": "company_id_hashed",
 "isMultiKey": false,
 "isUnique": false,
 "isSparse": false,
 "isPartial": false,
 "indexVersion": NumberInt("2"),
 "direction": "forward",
 "indexBounds": {
 "company_id": [
 "[7977521071453068053, 7977521071453068053]"

这部分显示只用到了company_id_hashed索引,没有用到create_time_1索引。

rejectedPlans

"stage": "SHARDING_FILTER",
 "inputStage": {
 "stage": "FETCH",
 "filter": {
 "company_id": {
 "$eq": 13272
 }
 },
 "inputStage": {
 "stage": "IXSCAN",
 "keyPattern": {
 "create_time": 1
 },
 "indexName": "create_time_1",
 "isMultiKey": false,
 "multiKeyPaths": {
 "create_time": [ ]
 },
 "isUnique": false,
 "isSparse": false,
 "isPartial": false,
 "indexVersion": NumberInt("2"),
 "direction": "forward",
 "indexBounds": {
 "create_time": [
 "[new Date(1712764800000), new Date(1713369599000)]"
 ]
 }
 }
 }
 },
 {
 "stage": "SHARDING_FILTER",
 "inputStage": {
 "stage": "FETCH",
 "filter": {
 "$and": [
 {
 "create_time": {
 "$lte": ISODate("2024-04-17T15:59:59.000Z")
 }
 },
 {
 "create_time": {
 "$gte": ISODate("2024-04-10T16:00:00.000Z")
 }
 }
 ]
 },
 "inputStage": {
 "stage": "IXSCAN",
 "keyPattern": {
 "company_id": 1
 },
 "indexName": "company_id_1",
 "isMultiKey": false,
 "multiKeyPaths": {
 "company_id": [ ]
 },

这部分显示的是被拒绝的执行计划列表,不会用到company_id_1、create_time_1索引。

executionStats

"nReturned": NumberInt("229707"),
 "executionTimeMillis": NumberInt("15668"),
 "totalKeysExamined": NumberInt("238012"),
 "totalDocsExamined": NumberInt("238012"),
 "executionStages": {
 "stage": "SINGLE_SHARD",
 "nReturned": NumberInt("229707"),
 "executionTimeMillis": NumberInt("15668"),
 "totalKeysExamined": NumberInt("238012"),
 "totalDocsExamined": NumberInt("238012"),
 "totalChildMillis": NumberLong("15667"),
 "shards": [
 {
 "shardName": "d-m5eee03fdeaeaee4",
 "executionSuccess": true,
 "executionStages": {
 "stage": "SHARDING_FILTER",
 "nReturned": NumberInt("229707"),
 "executionTimeMillisEstimate": NumberInt("14996"),
 "works": NumberInt("238013"),
 "advanced": NumberInt("229707"),
 "needTime": NumberInt("8305"),
 "needYield": NumberInt("0"),
 "saveState": NumberInt("1980"),
 "restoreState": NumberInt("1980"),
 "iseoF": NumberInt("1"),
 "chunkSkips": NumberInt("0"),
 "inputStage": {
 "stage": "FETCH",
 "filter": {
 "$and": [
 {
 "company_id": {
 "$eq": 13272
 }
 },
 {
 "create_time": {
 "$lte": ISODate("2024-04-17T15:59:59.000Z")
 }
 },
 {
 "create_time": {
 "$gte": ISODate("2024-04-10T16:00:00.000Z")
 }
 }
 ]
 },
 "nReturned": NumberInt("229707"),
 "executionTimeMillisEstimate": NumberInt("14595"),
 "works": NumberInt("238013"),
 "advanced": NumberInt("229707"),
 "needTime": NumberInt("8305"),
 "needYield": NumberInt("0"),
 "saveState": NumberInt("1980"),
 "restoreState": NumberInt("1980"),
 "isEOF": NumberInt("1"),
 "docsExamined": NumberInt("238012"),
 "alreadyHasObj": NumberInt("0"),
 "inputStage": {
 "stage": "IXSCAN",
 "nReturned": NumberInt("238012"),
 "executionTimeMillisEstimate": NumberInt("251"),
 "works": NumberInt("238013"),
 "advanced": NumberInt("238012"),
 "needTime": NumberInt("0"),
 "needYield": NumberInt("0"),
 "saveState": NumberInt("1980"),
 "restoreState": NumberInt("1980"),
 "isEOF": NumberInt("1"),
 "keyPattern": {
 "company_id": "hashed"
 },
 "indexName": "company_id_hashed",
 "isMultiKey": false,
 "isUnique": false,
 "isSparse": false,
 "isPartial": false,
 "indexVersion": NumberInt("2"),
 "direction": "forward",
 "indexBounds": {
 "company_id": [
 "[7977521071453068053, 7977521071453068053]"
 ]
 },
 "keysExamined": NumberInt("238012"),
 "seeks": NumberInt("1"),
 "dupsTested": NumberInt("0"),
 "dupsDropped": NumberInt("0")

这部分显示的是查询的执行统计信息。

索引分析

通过explain的执行计划,可以看出索引的使用上存在问题。按理说company_id、create_time都已创建索引,为什么没有使用上?是什么原因导致它失效,没有用上create_time索引?

下面列举了失效的情况:

  • 索引选择性不高:由于查询条件是一个范围查询,create_time 字段可能有许多不同的值满足条件。因此,单键索引 create_time_1 的选择性(即索引中不同值的比例)可能不高,这使得使用该索引无法有效地减少需要检索的文档数量。
  • 查询需要跨越多个索引键值:查询涉及到了两个字段 company_id 和 create_time。虽然索引 create_time_1 可以帮助过滤 create_time 符合条件的文档,但在执行查询时,还需要考虑 company_id 的匹配条件。因此,MongoDB 需要在两个索引之间进行查找和合并,而不是简单地使用单个索引来解决查询。
  • 额外的查找和合并成本:在涉及多个条件的查询中,MongoDB 会尝试使用覆盖索引(Covered Index)来尽可能地减少在磁盘上的文档检索。然而,在这种情况下,create_time_1 索引不能单独满足查询条件,因此 MongoDB 还需要查找和合并从 company_id_1 索引中过滤出来的文档。这种额外的查找和合并过程会增加查询的成本,并且降低性能。
    因此,针对给定的查询语句,MongoDB 不会使用 create_time_1 索引来优化查询,而是会选择其他更适合的索引,如 company_id_hashed 和 company_id_1。

问题原因

造成执行耗时过长的主要原因是索引失效的问题,在涉及多个条件的查询中,MongoDB 会尝试使用覆盖索引(Covered Index)来尽可能地减少在磁盘上的文档检索。然而,在这种情况下,create_time_1 索引不能单独满足查询条件,因此 MongoDB 还需要查找和合并从 company_id_1 索引中过滤出来的文档。这种额外的查找和合并过程会增加查询的成本,并且降低性能。

优化方案

创建新的复合索引company_id_create_time,让其走company_id_hashed到company_id_create_time的链路。添加新的索引后,相同的语句执行时间只需要400ms,能满足业务的需求。

结论

要多关注索引在什么情况下会失效?复合索引的先后顺序,不是每个条件字段都建个单个普通索引,查询语句都会使用上,不要存在这种误区,有时候复合索引才是最完美的组合。

执行计划详解

1、queryPlanner:包含了MongoDB查询的执行计划。

  • mongosPlannerVersion:MongoDB计划版本。
  • winningPlan:胜出的执行计划,即MongoDB选择的最佳执行计划。
  • shards: 分片的详细信息,包括分片名称、连接字符串、服务器信息等。2、winningPlan: 胜出的执行计划。
  • stage: 执行阶段,这里是SINGLE_SHARD,表示单分片操作。
  • shardName: 执行操作的分片名称。
  • plannerVersion: 计划版本。
  • namespace: 查询的命名空间。
  • indexFilterSet: 是否设置了索引过滤器。
  • parsedQuery: 解析后的查询条件。
  • winningPlan: 胜出的执行计划的详细信息,这里是SHARDING_FILTER。3、rejectedPlans: 被拒绝的执行计划列表,即非胜出的备选计划。
    每个被拒绝的执行计划包含了其详细信息,包括执行阶段、过滤器、索引扫描等。
    4、executionStats: 查询的执行统计信息。
  • nReturned: 返回的文档数量。
  • executionTimeMillis: 查询执行时间(毫秒)。
  • totalKeysExamined: 总共检查的键数量。
  • totalDocsExamined: 总共检查的文档数量。
  • executionStages: 执行阶段的详细统计信息。

作者介绍

吴守阳,51CTO社区编辑,拥有8年DBA工作经验,熟练管理MySQLredis、MongoDB等开源数据库。精通性能优化、备份恢复和高可用性架构设计。善于故障排除和自动化运维,保障系统稳定可靠。具备良好的团队合作和沟通能力,致力于为企业提供高效可靠的数据库解决方案。



Tags:MongoDB   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除。
▌相关推荐
线上MongoDB查询慢,如何通过索引优化直降响应时间?
作者 | 吴守阳审校 | 重楼背景线上某个页面的响应速度异常缓慢,达到了16秒,严重影响了业务的正常运行。经过与研发的沟通得知,该页面调用的数据集合只会保留7天的数据,集合有600...【详细内容】
2024-04-29  Search: MongoDB  点击:(0)  评论:(0)  加入收藏
MongoDB 大量数据插入时的性能影响及解决方法
MongoDB 是一种广泛应用的 NoSQL 数据库,以其高度可扩展性和灵活性而闻名。然而,在处理大量数据时,MongoDB 的性能可能会受到一些影响。大量数据插入对 MongoDB 性能的影响磁盘...【详细内容】
2024-01-05  Search: MongoDB  点击:(140)  评论:(0)  加入收藏
Java操作MongoDB如何批量写入数据
当需要插入、更新或删除大量文档时,一次执行多个操作比分别执行每个操作要快得多。批量操作减少了网络往返次数,减少了I/O负载,并且可能允许数据库引擎更有效地利用内部缓存和...【详细内容】
2023-12-19  Search: MongoDB  点击:(103)  评论:(0)  加入收藏
解析MongoDB的并发控制和事务隔离级别:保证数据一致性
MongoDB 是一个高性能的文档型数据库,支持多维度的并发控制和事务隔离级别,以保证数据一致性。接下来,下面将详细讲解 MongoDB 的并发控制和事务隔离级别。一、并发控制MongoDB...【详细内容】
2023-12-19  Search: MongoDB  点击:(108)  评论:(0)  加入收藏
MongoDB与大数据处理:构建高性能分布式数据库
MongoDB是一种非关系型数据库,具有高度灵活性和可扩展性。在处理大量数据时,索引的优化是提升查询性能的关键。下面将介绍一些MongoDB索引优化的指南,帮助用户更好地利用索引来...【详细内容】
2023-12-18  Search: MongoDB  点击:(75)  评论:(0)  加入收藏
MongoDB索引优化指南:提升查询性能的关键
MongoDB是一种非关系型数据库,具有高度灵活性和可扩展性。在处理大量数据时,索引的优化是提升查询性能的关键。下面将介绍一些MongoDB索引优化的指南,帮助用户更好地利用索引来...【详细内容】
2023-12-14  Search: MongoDB  点击:(127)  评论:(0)  加入收藏
Mongodb和Elasticsearch计算经纬度哪个性能更好
MongoDB和Elasticsearch都支持计算经纬度距离,但它们的性能表现可能因使用场景和数据规模而异。性能对比1、数据索引和存储 MongoDB使用地理空间索引(2dsphere)来支持经纬度数...【详细内容】
2023-12-11  Search: MongoDB  点击:(251)  评论:(0)  加入收藏
解密MongoDB集群管理:构建高可用性数据库架构
MongoDB集群管理是指在MongoDB数据库环境中构建高可用性的数据库架构,以确保数据的持久性、可用性和性能。下面将详细介绍构建高可用性数据库架构的相关概念、方法和步骤。一...【详细内容】
2023-12-06  Search: MongoDB  点击:(155)  评论:(0)  加入收藏
Java和MongoDB的异常检测
构建实时监控和告警系统是当今许多企业和组织所需要的关键功能之一。Java和MongoDB的异常检测是这样的一个监控系统中的重要组成部分。下面将详细介绍如何使用Java和MongoDB...【详细内容】
2023-11-23  Search: MongoDB  点击:(218)  评论:(0)  加入收藏
构建高可用的MongoDB部署架构:应对故障和灾难恢复
MongoDB 是一种流行的 NoSQL 数据库,广泛用于各种规模的应用程序。为了确保数据的高可用性和灾难恢复能力,构建一个可靠的 MongoDB 部署架构至关重要。本文将重点介绍如何构建...【详细内容】
2023-11-21  Search: MongoDB  点击:(214)  评论:(0)  加入收藏
▌简易百科推荐
线上MongoDB查询慢,如何通过索引优化直降响应时间?
作者 | 吴守阳审校 | 重楼背景线上某个页面的响应速度异常缓慢,达到了16秒,严重影响了业务的正常运行。经过与研发的沟通得知,该页面调用的数据集合只会保留7天的数据,集合有600...【详细内容】
2024-04-29    51CTO  Tags:MongoDB   点击:(0)  评论:(0)  加入收藏
向量数据库落地实践
本文基于京东内部向量数据库vearch进行实践。Vearch 是对大规模深度学习向量进行高性能相似搜索的弹性分布式系统。详见: https://github.com/vearch/zh_docs/blob/v3.3.X/do...【详细内容】
2024-04-03  京东云开发者    Tags:向量数据库   点击:(15)  评论:(0)  加入收藏
原来 SQL 函数是可以内联的!
介绍在某些情况下,SQL 函数(即指定LANGUAGE SQL)会将其函数体内联到调用它的查询中,而不是直接调用。这可以带来显著的性能提升,因为函数体可以暴露给调用查询的规划器,从而规划器...【详细内容】
2024-04-03  红石PG  微信公众号  Tags:SQL 函数   点击:(11)  评论:(0)  加入收藏
如何正确选择NoSQL数据库
译者 | 陈峻审校 | 重楼Allied Market Research最近发布的一份报告指出,业界对于NoSQL数据库的需求正在持续上升。2022年,全球NoSQL市场的销售额已达73亿美元,预计到2032年将达...【详细内容】
2024-03-28    51CTO  Tags:NoSQL   点击:(24)  评论:(0)  加入收藏
为什么数据库连接池不采用 IO 多路复用?
这是一个非常好的问题。IO多路复用被视为是非常好的性能助力器。但是一般我们在使用DB时,还是经常性采用c3p0,tomcat connection pool等技术来与DB连接,哪怕整个程序已经变成以...【详细内容】
2024-03-27  dbaplus社群    Tags:数据库连接池   点击:(23)  评论:(0)  加入收藏
八个常见的数据可视化错误以及如何避免它们
在当今以数据驱动为主导的世界里,清晰且具有洞察力的数据可视化至关重要。然而,在创建数据可视化时很容易犯错误,这可能导致对数据的错误解读。本文将探讨一些常见的糟糕数据可...【详细内容】
2024-03-26  DeepHub IMBA  微信公众号  Tags:数据可视化   点击:(16)  评论:(0)  加入收藏
到底有没有必要分库分表,如何考量的
关于是否需要进行分库分表,可以根据以下考量因素来决定: 数据量和负载:如果数据量巨大且负载压力较大,单一库单一表可能无法满足性能需求,考虑分库分表。 数据增长:预估数据增长...【详细内容】
2024-03-20  码上遇见你  微信公众号  Tags:分库分表   点击:(28)  评论:(0)  加入收藏
在 SQL 中写了 in 和 not in,技术总监说要炒了我……
WHY?IN 和 NOT IN 是比较常用的关键字,为什么要尽量避免呢?1、效率低项目中遇到这么个情况:t1表 和 t2表 都是150w条数据,600M的样子,都不算大。但是这样一句查询 ↓select *...【详细内容】
2024-03-18  dbaplus社群    Tags:SQL   点击:(22)  评论:(0)  加入收藏
应对慢SQL的致胜法宝:7大实例剖析+优化原则
大促备战,最大的隐患项之一就是慢SQL,对于服务平稳运行带来的破坏性最大,也是日常工作中经常带来整个应用抖动的最大隐患,在日常开发中如何避免出现慢SQL,出现了慢SQL应该按照什...【详细内容】
2024-03-14  京东云开发者    Tags:慢SQL   点击:(17)  评论:(0)  加入收藏
过去一年,我看到了数据库领域的十大发展趋势
作者 | 朱洁策划 | 李冬梅过去一年,行业信心跌至冰点2022 年中,红衫的一篇《适应与忍耐》的报告,对公司经营提出了预警,让各个公司保持现金流,重整团队,想办法增加盈利。这篇报告...【详细内容】
2024-03-12    InfoQ  Tags:数据库   点击:(40)  评论:(0)  加入收藏
站内最新
站内热门
站内头条