论MongoDB索引选择的重要性

本文涉及的产品
云原生多模数据库 Lindorm,多引擎 多规格 0-4节点
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
云数据库 MongoDB,通用型 2核4GB
简介:

线上某业务,频繁出现IOPS 使用率100%的(每秒4000IOPS)现象,每次持续接近1个小时,从慢请求的日志发现是一个 getMore 请求耗时1个小时,导致IOPS高;深入调查之后,最终发现竟是一个索引选择的问题。

IOPS

2017-11-01T15:04:17.498+0800 I COMMAND  [conn5735095] command db.mycoll command: getMore { getMore: 215174255789, collection: "mycoll" } cursorid:215174255789 keyUpdates:0 writeConflicts:0 numYields:161127 nreturned:8419 reslen:4194961 locks:{ Global: { acquireCount: { r: 322256 } }, Database: { acquireCount: { r: 161128 } }, Collection: { acquireCount: { r: 161128 } } } protocol:op_command 3651743ms
    

问题背景

业务每个整点开始,会把过去1小时的数据同步到另一个数据源,查询时会按 _id 排序,2个主要查询条件如下,先执行find命令,然后遍历cursor,读取所有满足条件的文档。

* created_at: { $gte: "2017-11-01 13:00:00", $lte: "2017-11-01 13:59:59" }
* sort: {_id: 1}

业务数据的特性

  • 每条数据插入时都带上 created_at 字段,时间为当前时间戳,并建立了 {created_at: -1} 的索引
  • _id 字段为用户自定义(并非mongodb默认的ObjectId),取值较随机,无规律
  • 整个集合非常大,总文档数超过1亿条

MongoDB的find、getMore特性

  • find命令,会返回第一批满足条件的batch(默认101条记录)以及一个cursor
  • getMore 根据find返回的cursor继续遍历,每次遍历默认返回不超过4MB的数据

索引的选择

方案1:使用 created_at 索引

整个执行路径为

  1. 通过 created_at 索引,快速定位到符合条件的文档
  2. 读出所有的满足 created_at 查询条件的文档
  3. 对所有的文档根据 _id 字段进行排序

如下是走这个索引的2条典型日志,可以看出

  • 符合 created_at 条件的文档大概有7w+,全部排序后,返回前101条,总共耗时约600ms;
  • 接下来 getMore,因为结果要按_id排序,getMore 还是得继续把所有符合条件的读出来排序,并跳过第一次的101条,返回下一批给客户端。

         2017-11-01T14:02:31.861+0800 I COMMAND  [conn5737355] command db.mycoll command: find { find: "mycoll", filter: { created_at: { $gte: "2017-11-01 13:00:00", $lte: "2017-11-01 13:59:59" } }, projection: { $sortKey: { $meta: "sortKey" } }, sort: { _id: 1 }, limit: 104000, shardVersion: [ Timestamp 5139000|7, ObjectId('590d9048c628ebe143f76863') ] } planSummary: IXSCAN { created_at: -1.0 } cursorid:215494987197 keysExamined:71017 docsExamined:71017 hasSortStage:1 keyUpdates:0 writeConflicts:0 numYields:557 nreturned:101 reslen:48458 locks:{ Global: { acquireCount: { r: 1116 } }, Database: { acquireCount: { r: 558 } }, Collection: { acquireCount: { r: 558 } } } protocol:op_command 598ms
         2017-11-01T14:02:32.036+0800 I COMMAND  [conn5737355] command db.mycoll command: getMore { getMore: 215494987197, collection: "mycoll" } cursorid:215494987197 keyUpdates:0 writeConflicts:0 numYields:66 nreturned:8510 reslen:4194703 locks:{ Global: { acquireCount: { r: 134 } }, Database: { acquireCount: { r: 67 } }, Collection: { acquireCount: { r: 67 } } } protocol:op_command 120ms
    

方案2:使用 _id 索引

整个执行路径为

  1. 根据 _id 索引,扫描所有的记录 (按_id索引的顺序扫描,对应的文档的created_at是随机的,无规律)
  2. 把满足 created_at 条件的文档返回,第一次find,要找到101个符合条件的文档返回

如下是走这个索引的2条典型日志,可以看出

  • 第一次扫描了17w,才找到101条符合条件的记录,耗时46s
  • 第二次要累计近4MB符合条件的文档(8419条)才返回,需要全表扫描更多的文档,最终耗时1个小时,由于全表扫描对cache非常不友好,所以一直是要从磁盘读取,所以导致大量的IO。

        2017-11-01T14:03:25.648+0800 I COMMAND  [conn5735095] command db.mycoll command: find { find: "mycoll", filter: { created_at: { $gte: "2017-11-01 13:00:00", $lte: "2017-11-01 13:59:59" } }, projection: { $sortKey: { $meta: "sortKey" } }, sort: { _id: 1 }, limit: 75000, shardVersion: [ Timestamp 5139000|7, ObjectId('590d9048c628ebe143f76863') ] } planSummary: IXSCAN { _id: 1 } cursorid:215174255789 keysExamined:173483 docsExamined:173483 fromMultiPlanner:1 replanned:1 keyUpdates:0 writeConflicts:0 numYields:2942 nreturned:101 reslen:50467 locks:{ Global: { acquireCount: { r: 5886 } }, Database: { acquireCount: { r: 2943 } }, Collection: { acquireCount: { r: 2943 } } } protocol:op_command 46232ms
        2017-11-01T15:04:17.498+0800 I COMMAND  [conn5735095] command db.mycoll command: getMore { getMore: 215174255789, collection: "mycoll" } cursorid:215174255789 keyUpdates:0 writeConflicts:0 numYields:161127 nreturned:8419 reslen:4194961 locks:{ Global: { acquireCount: { r: 322256 } }, Database: { acquireCount: { r: 161128 } }, Collection: { acquireCount: { r: 161128 } } } protocol:op_command 3651743ms
    

总结

IOPS高是因为选择的索引不是最优,那为什么MongoDB没有选择最优的索引来执行这个任务呢?

  • 从日志可以看出,绝大部分情况,MongoDB 都是走的 created_at 索引
  • 上述case,那个索引更优,其实是跟数据的分布情况相关的

    • 如果满足 created_at 查询条件的文档特别多,那么对大量的文档排序的开销也是很大的
    • 如果 created_at 字段分布非常离散(如本案例中的数据),则全表扫描找出符合条件的文档开销更大
  • MongoDB 的索引是基于采样代价模型,一个索引对采样的数据集更优,并不意味着其对整个数据集也最优

    • MongoDB 一个查询第一次执行时,如果有多个执行计划,会根据模型选出最优的,并缓存起来,以提升效率
    • 当 MongoDB 发生集合创建/删除索引时,会将缓存的执行计划清空掉,并重新选择
    • MongoDB 在执行的过程中,也会根据执行计划的表现,比如一个执行计划,很多次迭代都没遇到符合条件的文档,就会考虑这个执行计划是否最优了,会触发重新构建执行计划的逻辑(具体触发的策略还没有详细研究,后续再分享),比如方案2里的find查询,执行计划里包含了 {replanned: 1} 说明是重新构建了执行计划;当它发现这个执行计划实际执行起来效果更差时,最终还是会会到更优的执行计划上。
  • 最懂数据的还是业务自身,对于查询优化器搞不定的case,可以通过在查询时加 hint,自己指定的索引来构建执行计划。
相关实践学习
MongoDB数据库入门
MongoDB数据库入门实验。
快速掌握 MongoDB 数据库
本课程主要讲解MongoDB数据库的基本知识,包括MongoDB数据库的安装、配置、服务的启动、数据的CRUD操作函数使用、MongoDB索引的使用(唯一索引、地理索引、过期索引、全文索引等)、MapReduce操作实现、用户管理、Java对MongoDB的操作支持(基于2.x驱动与3.x驱动的完全讲解)。 通过学习此课程,读者将具备MongoDB数据库的开发能力,并且能够使用MongoDB进行项目开发。   相关的阿里云产品:云数据库 MongoDB版 云数据库MongoDB版支持ReplicaSet和Sharding两种部署架构,具备安全审计,时间点备份等多项企业能力。在互联网、物联网、游戏、金融等领域被广泛采用。 云数据库MongoDB版(ApsaraDB for MongoDB)完全兼容MongoDB协议,基于飞天分布式系统和高可靠存储引擎,提供多节点高可用架构、弹性扩容、容灾、备份回滚、性能优化等解决方案。 产品详情: https://www.aliyun.com/product/mongodb
相关文章
|
1月前
|
监控 NoSQL MongoDB
【MongoDB】MongoDB 索引
【4月更文挑战第1天】【MongoDB】MongoDB 索引
|
1月前
|
存储 NoSQL 关系型数据库
|
11天前
|
NoSQL 定位技术 MongoDB
解锁MongoDB索引的秘密:优化查询效率与应对限制的策略
解锁MongoDB索引的秘密:优化查询效率与应对限制的策略
|
11天前
|
NoSQL 定位技术 MongoDB
深入探索 MongoDB:高级索引解析与优化策略
深入探索 MongoDB:高级索引解析与优化策略
|
11天前
|
存储 NoSQL MongoDB
MongoDB 集合创建指南:命名规范、索引优化和数据模型设计
MongoDB 集合创建指南:命名规范、索引优化和数据模型设计
|
11天前
|
存储 监控 NoSQL
MongoDB 覆盖索引查询:提升性能的完整指南
MongoDB 覆盖索引查询:提升性能的完整指南
|
11天前
|
NoSQL MongoDB 数据库
MongoDB索引:加速查询、提升性能的利器
MongoDB索引:加速查询、提升性能的利器
|
25天前
|
NoSQL MongoDB 数据库
MongoDB的索引与索引字段的顺序
MongoDB的索引与索引字段的顺序
35 2
|
27天前
|
NoSQL MongoDB 数据库
通过优化索引以消除 MongoDB 中的 "查询目标已超过1000个扫描对象/返回的文档数" 警告
MongoDB NoSQL数据库在处理复杂查询时可能出现“查询目标已超过1000个扫描对象/返回的文档数”警告。文章分析了该问题,展示了一个示例集合和相关索引,并提供了查询示例。通过`explain`命令发现查询未有效利用索引。解决方案是遵循ESR规则,创建新索引从而优化查询并消除警告。
53 1
|
1月前
|
NoSQL 测试技术 定位技术
【MongoDB 专栏】MongoDB 的地理空间索引与位置查询
【5月更文挑战第10天】MongoDB 支持地理空间数据处理,提供2dsphere(球面)和2d(平面)索引,适用于地图导航、物流、社交网络等领域。通过创建索引,可加速位置查询,如查询范围、最近邻及地理空间聚合。案例包括地图应用、物流追踪和社交网络。注意数据准确性、索引优化和性能测试,以发挥其在地理空间处理中的潜力。学习此功能,为应用开发解锁更多可能性!
【MongoDB 专栏】MongoDB 的地理空间索引与位置查询

相关产品

  • 云数据库 MongoDB 版