【MongoDB】MongoDB的核心-索引原理及索引优化、及查询聚合优化实战案例(超详细)

在这里插入图片描述

文章目录

    • 一、数据库查询效率问题引出索引需求
    • 二、索引的基本原理及作用
      • (一)索引的创建及数据组织
      • (二)不同类型的索引
      • (三)索引的额外属性
    • 三、索引的优化与查询计划分析
      • (一)通过profiling监测慢请求
      • (二)查询计划分析优化索引使用
    • 四、查询聚合优化
      • (一)案例背景
        • 问题描述
        • 问题分析
          • 1. 定位慢查询
          • 2. 分析慢查询语句
            • 第一步:`$match`操作
            • 第二步:`$project`操作
            • 第三步:`$group`操作
        • 查看DB/Server/Collection的状态
          • 1. DB状态
          • 2. 查看`orders`这个collection的状态
        • 性能优化
          • 1. 性能优化 - 索引
          • 2. 性能优化 - 聚合大量数据
        • 小结

更多相关内容可查看

一、数据库查询效率问题引出索引需求

当在使用MongoDB等数据库进行集合查询时,如果遇到查询效率低下的情况,就可能需要考虑使用索引了。以MongoDB为例,在向集合插入多个文档后,每个文档经过底层存储引擎持久化会有一个位置信息(如mmapv1引擎里是『文件id + 文件内offset』,wiredtiger存储引擎里是其生成的一个key),通过这个位置信息能从存储引擎里读出该文档。

mongo-9552:PRIMARY> db.person.find()
{ "_id" : ObjectId("571b5da31b0d530a03b3ce82"), "name" : "jack", "age" : 19 }
{ "_id" : ObjectId("571b5dae1b0d530a03b3ce83"), "name" : "rose", "age" : 20 }
{ "_id" : ObjectId("571b5db81b0d530a03b3ce84"), "name" : "jack", "age" : 18 }
{ "_id" : ObjectId("571b5dc21b0d530a03b3ce85"), "name" : "tony", "age" : 21 }
{ "_id" : ObjectId("571b5dc21b0d530a03b3ce86"), "name" : "adam", "age" : 18 }

假设要执行一个查询操作,比如db.person.find( {age: 18} ),如果没有索引,就需要遍历所有的文档(即进行“全表扫描”),根据位置信息读出文档后,再对比age字段是否为18。当集合文档数量较少时,全表扫描的开销可能不大,但当文档数量达到百万、千万甚至上亿时,全表扫描的开销会非常大,一个查询耗费数十秒甚至几分钟都有可能。

二、索引的基本原理及作用

(一)索引的创建及数据组织

比如上面的例子里,person集合里包含插入了5个文档,假设其存储后位置信息如下

位置信息文档
pos1{“name” : “jack”, “age” : 19 }
pos2{“name” : “rose”, “age” : 20 }
pos3{“name” : “jack”, “age” : 18 }
pos4{“name” : “tony”, “age” : 21}
pos5{“name” : “adam”, “age” : 18}

如果想加速 db.person.find( {age: 18} ),就可以考虑对person表的age字段建立索引。

db.person.createIndex( {age: 1} )  // 按age字段创建升序索引

建立索引后,MongoDB会额外存储一份按age字段升序排序的索引数据,索引结构类似如下,索引通常采用类似btree的结构持久化存储,以保证从索引里快速(O(logN)的时间复杂度)找出某个age值对应的位置信息,然后根据位置信息就能读取出对应的文档。

age位置信息
18pos3
18pos5
19pos1
20pos2
21pos4

简单来说,索引就是将文档按照某个(或某些)字段顺序组织起来,以便能根据该字段高效地进行查询。它至少能优化以下场景的效率:

  • 查询场景:比如查询年龄为18的所有人,有了索引就无需全表扫描,可直接通过索引快速定位到符合条件的文档。
  • 更新/删除场景:在将年龄为18的所有人的信息进行更新或删除时,因为更新或删除操作需要先根据条件查询出所有符合条件的文档,所以本质上也是在优化查询环节。
  • 排序场景:将所有人的信息按年龄排序时,如果没有索引,需要全表扫描文档,然后再对扫描的结果进行排序;而有了索引,可利用索引的有序性更高效地完成排序。

MongoDB默认会为插入的文档生成_id字段(如果应用本身没有指定该字段),并且为了保证能根据文档id快速查询文档,MongoDB默认会为集合创建_id字段的索引。

mongo-9552:PRIMARY> db.person.getIndexes() // 查询集合的索引信息
[{"ns" : "test.person",  // 集合名"v" : 1,               // 索引版本"key" : {              // 索引的字段及排序方向"_id" : 1           // 根据_id字段升序索引},"name" : "_id_"        // 索引的名称}
]

(二)不同类型的索引

MongoDB支持多种类型的索引,每种类型适用于不同的使用场合:

  • 单字段索引(Single Field Index)

    • 通过db.person.createIndex( {age: 1} )语句可针对age创建单字段索引,能加速对age字段的各种查询请求,是最常见的索引形式,MongoDB默认创建的_id索引也属于这种类型。
    • {age: 1}代表升序索引,也可通过{age: -1}来指定降序索引,对于单字段索引,升序/降序效果是一样的。
      db.person.createIndex( {age: 1, name: 1} ) 
      
  • 复合索引 (Compound Index)

    • 它是单字段索引的升级版本,针对多个字段联合创建索引,先按第一个字段排序,第一个字段相同的文档按第二个字段排序,依次类推。例如,通过db.person.createIndex( {age: 1, name: 1} )可针对agename这2个字段创建一个复合索引。
    • 复合索引能满足的查询场景比单字段索引更丰富,不光能满足多个字段组合起来的查询(如db.person.find( {age: 18, name: "jack"} )),也能满足匹配复合索引前缀的查询(如{age: 1}{age: 1, name: 1}的前缀,所以db.person.find( {age: 18} )的查询也能通过该索引来加速),但像db.person.find( {name: "jack"} )这种只涉及部分字段且不符合前缀规则的查询则无法使用该复合索引。在创建复合索引时,字段的顺序除了受查询需求影响,还需考虑字段的值分布情况。比如age字段取值有限,相同age的文档较多,而name字段取值丰富,相同name的文档较少,此时先按name字段查找,再在相同name的文档里查找age字段会更为高效。
      db.person.createIndex( {name: 1, age: 1} ) 
      
  • 多key索引 (Multikey Index)

    • 当索引的字段为数组时,创建出的索引称为多key索引。例如,在person表加入一个habbit字段(数组)用于描述兴趣爱好,通过db.person.createIndex( {habbit: 1} )可自动创建多key索引,用于查询有相同兴趣爱好的人。
       {"name" : "jack", "age" : 19, habbit: ["football, runnning"]}db.person.createIndex( {habbit: 1} )  // 自动创建多key索引db.person.find( {habbit: "football"} )
      
  • 其他类型索引

    • 哈希索引(Hashed Index):按照某个字段的hash值来建立索引,目前主要用于MongoDB Sharded Cluster的Hash分片,hash索引只能满足字段完全匹配的查询,不能满足范围查询等。
    • 地理位置索引(Geospatial Index):能很好地解决O2O的应用场景,比如“查找附近的美食”、“查找某个区域内的车站”等。
    • 文本索引(Text Index):能解决快速文本查找的需求,比如对于一个博客文章集合,可针对博客的内容建立文本索引,以便根据博客内容快速查找。

(三)索引的额外属性

MongoDB除了支持多种不同类型的索引,还能对索引定制一些特殊的属性:

  • 唯一索引 (unique index):保证索引对应的字段不会出现相同的值,比如_id索引就是唯一索引。
  • TTL索引:可以针对某个时间字段,指定文档的过期时间(经过指定时间后过期 或 在某个时间点过期)。
  • 部分索引 (partial index):只针对符合某个特定条件的文档建立索引,在3.2版本才支持该特性。
  • 稀疏索引(sparse index):只针对存在索引字段的文档建立索引,可看做是部分索引的一种特殊情况。

三、索引的优化与查询计划分析

(一)通过profiling监测慢请求

MongoDB支持对DB的请求进行profiling,目前支持3种级别的profiling:

  • 0级:不开启profiling。
  • 1级:将处理时间超过某个阈值(默认100ms)的请求都记录到DB下的system.profile集合(类似于mysql、redis的slowlog),生产环境通常建议使用此级别,并根据自身需求配置合理的阈值,用于监测慢请求的情况,以便及时进行索引优化。
  • 2级:将所有的请求都记录到DB下的system.profile集合,生产环境需慎用。

(二)查询计划分析优化索引使用

当索引已经建立了,但查询还是很慢时,就需要深入分析索引的使用情况,可通过查看详细的查询计划来决定如何优化。通过执行计划可以看出以下问题:

  • 根据某个/些字段查询,但没有建立索引。
  • 根据某个/些字段查询,但建立了多个索引,执行查询时没有使用预期的索引。

例如,建立索引前,db.person.find( {age: 18} )必须执行COLLSCAN(全表扫描);

mongo-9552:PRIMARY> db.person.find({age: 18}).explain()
{"queryPlanner" : {"plannerVersion" : 1,"namespace" : "test.person","indexFilterSet" : false,"parsedQuery" : {"age" : {"$eq" : 18}},"winningPlan" : {"stage" : "COLLSCAN","filter" : {"age" : {"$eq" : 18}},"direction" : "forward"},"rejectedPlans" : [ ]},"serverInfo" : {"host" : "localhost","port" : 9552,"version" : "3.2.3","gitVersion" : "b326ba837cf6f49d65c2f85e1b70f6f31ece7937"},"ok" : 1
}

建立索引后,通过查询计划可以看出,先进行[IXSCAN](从索引中查找),然后FETCH`,读取出满足条件的文档。

mongo-9552:PRIMARY> db.person.find({age: 18}).explain()
{"queryPlanner" : {"plannerVersion" : 1,"namespace" : "test.person","indexFilterSet" : false,"parsedQuery" : {"age" : {"$eq" : 18}},"winningPlan" : {"stage" : "FETCH","inputStage" : {"stage" : "IXSCAN","keyPattern" : {"age" : 1},"indexName" : "age_1","isMultiKey" : false,"isUnique" : false,"isSparse" : false,"isPartial" : false,"indexVersion" : 1,"direction" : "forward","indexBounds" : {"age" : ["[18.0, 18.0]"]}}},"rejectedPlans" : [ ]},"serverInfo" : {"host" : "localhost","port" : 9552,"version" : "3.2.3","gitVersion" : "b326ba837cf6f49d65c2f85e1b70f6f31ece7937"},"ok" : 1
}

需要注意的是,索引并不是越多越好,集合的索引太多,会影响写入、更新的性能,因为每次写入都需要更新所有索引的数据。所以system.profile里的慢请求可能是索引建立得不够导致,也可能是索引过多导致。

四、查询聚合优化

(一)案例背景

我们有一个电商订单分析系统,使用MongoDB存储订单数据。当执行一个分析接口,获取特定店铺在某一周内的订单商品分类统计信息时,发现查询速度非常慢,严重影响用户体验。

问题描述

执行订单分析接口,查询特定店铺(假设店铺ID为“20001”)在某一周(2024 - 05 - 01T00:00:00.000Z到2024 - 05 - 07T23:59:59.999Z)内的订单商品分类统计,需要花费约12秒,这明显不符合性能要求。

问题分析
1. 定位慢查询

首先查看当前mongo profile的级别,通过db.getProfilingLevel()发现其为0,即默认没有记录。设置profile级别为记录慢查询模式,设置阈值为1000ms,即db.setProfilingLevel(1, 1000)。再次执行订单分析查询接口,查看Profile记录。

2. 分析慢查询语句

通过查看Profile记录,发现执行的查询是一个聚合管道(pipeline):

第一步:$match操作
{"$match": {"storeId": "20001","$and": [{"orderTime": {"$gte": ISODate("2024-05-01T00:00:00.000Z"),"$lte": ISODate("2024-05-07T23:59:59.999Z")}}]}
}

用于匹配店铺ID为“20001”且订单时间在指定一周内的订单记录。

第二步:$project操作
{"$project": {"productCategory": 1,"orderDate": {"$concat": [{"$substr": [{"$year": ["$orderTime"]},0,4]},"-",{"$substr": [{"$month": ["$orderTime"]},0,2]},"-",{"$substr": [{"$dayOfMonth": ["$orderTime"]},0,2]}]}}
}

除了提取productCategory字段外,还对orderTime字段进行处理,拼接为“yyyy - MM - dd”格式,并将其命名为orderDate

第三步:$group操作
{"$group": {"_id": {"orderDate": "$orderDate","productCategory": "$productCategory"},"count": {"$sum": 1}}
}

orderDateproductCategory进行分组,统计不同日期和商品分类对应的订单数量。

从Profile中可以看到相关指标:

  • millis:花费了12010毫秒返回查询结果。
  • ts:命令执行时间。
  • info:命令内容。
  • query:代表查询。
  • nsecommerce.orders(代表查询的库与集合)。
  • nreturned:返回记录数及用时。
  • reslen:返回的结果集大小(字节数)。
  • nscanned:扫描记录数量。

发现nscanned数很大,接近记录总数,可能没有使用索引查询。

查看DB/Server/Collection的状态
1. DB状态

查看数据库整体状态,包括服务器版本、运行时间、连接数、各种操作计数器(如插入、查询、更新、删除等操作的次数)、存储引擎信息等。示例部分信息如下:

{"host": "ECOMMONGODB","version": "6.0.5","process": "mongod","pid": NumberLong(2005),"uptime": 12345678.0,"uptimeMillis": NumberLong(12345678901),"uptimeEstimate": NumberLong(12345678),"localTime": ISODate("2024-05-08T10:20:30.123Z"),"asserts": {"regular": 0,"warning": 0,"msg": 0,"user": 12345,"rollovers": 0},"connections": {"current": 120,"available": 800,"totalCreated": 13000},// 其他更多信息..."ok": 1.0
}
2. 查看orders这个collection的状态
{"ns": "ecommerce.orders","size": 987654321,"count": 3500000,"avgObjSize": 282,"storageSize": 234567890,"capped": false,"wiredTiger": {// wiredTiger存储引擎相关详细信息...},"nindexes": 1,"totalIndexSize": 30123456,"indexSizes": {"_id_": 30123456},"ok": 1.0
}
性能优化
1. 性能优化 - 索引

目前只有_id索引,接下来对orders集合创建storeIdorderTimeproductCategory字段的索引:

db.orders.ensureIndex({"storeId": 1, "orderTime": 1, "productCategory": 1});
db.orders.ensureIndex({"orderTime": 1});
db.orders.ensureIndex({"productCategory": 1});
db.orders.ensureIndex({"storeId": 1});

创建索引后,查询特定店铺一周内的订单商品分类统计信息,时间缩短到了500ms,效果显著。但当查询一个月的数据时,仍然需要15秒。

通过增加索引小结:添加索引解决了针对索引字段查询的效率问题,但对于大量数据的聚合操作,仅靠索引不能完全解决性能问题。例如,在没有索引的情况下,从500万条数据中找出特定店铺的订单可能需要全表扫描,耗时很长;而有了索引,命中索引查询(IXSCAN)速度提升明显。不过,对于聚合操作,随着数据量增大,性能问题依然存在。同时,判断效率优化情况应该看执行计划,而不仅仅是执行时间,因为执行时间可能受到多种因素影响。

2. 性能优化 - 聚合大量数据

对于这种查询聚合大量数据的问题,考虑到这是一个类似OLAP的操作,对其性能期望不能过高,因为大量数据的I/O操作远超OLTP操作。但仍有一定的优化空间:

  • 在订单插入或更新时,对每个店铺每天的每个商品分类的订单数量进行实时计数,并存储在一个专门的缓存集合中。例如,以{storeId: "20001", orderDate: "2024-05-01", productCategory: "Electronics", count: 10}的形式存储。
  • 每隔一段时间(如每天凌晨)对缓存集合进行一次完整的统计和更新,确保数据的准确性。这样在查询订单商品分类统计信息时,可以直接从缓存集合中获取数据,大大减少了查询和聚合的时间。
小结
  • 慢查询定位:通过Profile分析慢查询。
  • 查询优化:通过添加相应索引提升查询速度。
  • 聚合大数据方案:对于类似OLAP的聚合操作,要合理降低性能期望。从源头入手,在数据插入或更新时做好部分统计工作,缓存结果,以便在查询时直接使用,从而提升整体性能。同时,要结合执行计划来评估优化效果。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/59315.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

企业如何实现无缝数据中心进行大数据迁移呢?

数据中心迁移是企业面临的一个复杂而关键的挑战,涉及到大量的数据移动和系统的重新部署。随着业务的扩展和技术的进步,企业可能需要将数据中心迁移到新的位置或升级到更先进的设备。在进行数据迁移时,必须精心规划和执行,以确保数…

初始JavaEE篇 —— 网络编程(2):了解套接字,从0到1实现回显服务器

找往期文章包括但不限于本期文章中不懂的知识点: 个人主页:我要学编程程(ಥ_ಥ)-CSDN博客 所属专栏:JavaEE 目录 TCP 与 UDP Socket套接字 UDP TCP 网络基础知识 在一篇文章中,我们了解了基础的网络知识,网络的出…

【人工智能】10分钟解读-深入浅出大语言模型(LLM)——从ChatGPT到未来AI的演进

文章目录 一、前言二、GPT模型的发展历程2.1 自然语言处理的局限2.2 机器学习的崛起2.3 深度学习的兴起2.3.1 神经网络的训练2.3.2 神经网络面临的挑战 2.4 Transformer的革命性突破2.4.1 Transformer的核心组成2.4.2 Transformer的优势 2.5 GPT模型的诞生与发展2.5.1 GPT的核心…

最全最简单理解迭代器

1. 迭代器的基础概念(iterator) 1.1 本质 迭代器能够用来遍历容器的对象,与能够遍历数组的指针类似,是广义指针。 1.2 作用: 能够让迭代器与算法不干扰的相互发展,最后又能无间隙的粘合起来。重载了*,++,==,!=,=运算符。用以操作复杂的数据结构。容器提供迭代…

MTSET可溶于DMSO、DMF、THF等有机溶剂,并在水中有轻微的溶解性,91774-25-3

一、基本信息 中文名称:[2-(三甲基铵)乙基]甲硫基磺酸溴;MTSET巯基反应染料 英文名称:MTSET;[2-(Trimethylammonium)ethyl]methanethiosulfonate Bromide CAS号:91774-25-3 分子式:C6H16BrNO2S2 分子量…

CC1链学习记录

🌸 前言 上篇文章学习记录了URLDNS链,接下来学习一下Common-Colections利用链。 🌸 相关介绍 Common-Colections是Apache软件基金会的项目,对Java标准的Collections API提供了很好的补充,在其基础上对常用的数据结构…

数据结构PTA

20:C 22:B 27:D 填空 4-2:19 4-4:66 4-5:8 5-x:不加分号 ⬇:top p->next 编程 单链表 每个节点除了存放数据元素外,还要存储指向下一节点的指针…

【学术会议介绍,SPIE 出版】第四届计算机图形学、人工智能与数据处理国际学术会议 (ICCAID 2024,12月13-15日)

第四届计算机图形学、人工智能与数据处理国际学术会议 2024 4th International Conference on Computer Graphics, Artificial Intelligence and Data Processing (ICCAID 2024) 重要信息 大会官网:www.iccaid.net 大会时间:2024年12月13-15日 大会地…

【go从零单排】Command-Line Flags、Command-Line Subcommands命令行和子命令

🌈Don’t worry , just coding! 内耗与overthinking只会削弱你的精力,虚度你的光阴,每天迈出一小步,回头时发现已经走了很远。 📗概念 在 Go 语言中,命令行标志(Command-Line Flags&#xff09…

WEB攻防-通用漏洞SQL读写注入MYSQLMSSQLPostgraSQL

知识点: 1、SQL注入-MYSQL数据库; 2、SQL注入-MSSQL数据库; 3、SQL注入-PostgreSQL数据库; 首先要找到注入点 详细点: Access无高权限注入点-只能猜解,还是暴力猜解 MYSQL,PostgreSQL&am…

自定义springCloudLoadbalancer简述

概述 目前后端用的基本都是springCloud体系; 平时在dev环境开发时,会把自己的本地服务也注册上去,但是这样的话,在客户端调用时请求可能会打到自己本地,对客户端测试不太友好. 思路大致就是前端在请求头传入指定ip&a…

腾讯云11.11云服务器活动--上云拼团GO

目录 云服务器活动介绍: 轻量服务器 上GO拼团领券 云服务器购买 HAI现金券 学生专享GPU 活动总结 云服务器活动介绍: 双十一临近,这是您一年中最期待的购物狂欢时刻。作为国内领先的云计算服务商,腾讯云诚挚为您呈献前所未有的优惠福利,助您在这…

防脱生发最有效的产品,测评总结早看少踩雷

别人脱单、脱贫你脱发,就问你心酸不心酸?探索防脱之路确实不易,到底怎么才能有效防脱养发呢,很有必要分享一下我多年的防脱心得,这几款一直在用的防脱育发好物,秃头朋友们可以看看~ **露卡菲娅防脱精华液**…

c++:string(一)

文章目录 一string类1C语言中的字符串2C中的string二遍历1[ ]2迭代器3const迭代器4范围for5auto6总结三String的尾插1size和length2max_size,capacity和clear3访问接口4尾插字符和字符串5 append的重载三string的扩容问题(1)怎么扩容(2&#…

【2048】我的创作纪念日

机缘 2048天,不知不觉来csdn博客已经有2048天了,其实用csdn平台很久了,实际上写博客还是从2019年开始。 还记得最初成为创作者初心是什么吗? 最开始,主要是用来做笔记。平时工作中、学习中遇到的技术相关问题都会在cs…

壁仞科技上市前最后一波 校招 社招 内推

随着美国大选结束,国内GPU 产业得到空前的的发展空间,国内芯片相关股票一片飘红。 国内大型 GPU厂商壁仞科技,摩尔线程等正紧锣密鼓地加紧上市。 GPGPU 芯片赛道来到了史无前例的红利点,抓住机会💪 壁仞科技正在火热…

sql server 自动kill 查询超过20分钟的语句

起源于同事的烂sql 容易拖垮 数据服务器, 周末没有人监控数据库,好几次导致主从数据库同步失败 ,不得不自动kill 烂sql 语句如下 : -- 声明变量来存储超过20分钟的查询的会话ID DECLARE kill_sessions TABLE (session_id INT); …

GMS地下水数值模拟、全流程各工程类型地下水环境影响评价、MODFLOW Flex

GMS地下水数值模拟技术及在地下水环评中的应用 以地下水数值模拟软件GMS10.1操作为主要内容,在教学中强调三维地质结构建模、水文地质模型概化、边界条件设定、参数反演和模型校核等关键环节。不仅使学员掌握地下水数值模拟软件GMS10.1的全过程实际操作技术的基本技…

基于Multisim水箱水位控制系统仿真电路(含仿真和报告)

【全套资料.zip】水箱水位控制系统仿真电路Multisim仿真设计数字电子技术 文章目录 功能一、Multisim仿真源文件二、原理文档报告资料下载【Multisim仿真报告讲解视频.zip】 功能 1.在水箱内的不同高度安装3根金属棒,以感知水位变化情况, 液位分1&…

IPguard与Ping32全面对比——选择最适合企业的数据安全解决方案

在如今数据安全威胁日益加剧的时代,企业必须高度重视保护敏感数据与信息。因此,选择一款合适的数据安全软件,尤其是防泄密和信息保护软件,显得尤为重要。在市场上,有两款备受企业青睐的数据安全解决方案——IPguard和P…