说一说mongodb组合索引的匹配规则

embedded/2025/1/14 18:19:59/

一、背景

有一张1000多万条记录的大表,需要做归档至历史表,出现了大量慢查询。
查询条件是

"classroomId": {$in: ["xxx", "xxx", ..... "xxx","xxx", "xxx" ] }

耗时近5秒,且是全表扫描

在这里插入图片描述
为什么没有使用到任何索引呢?

请看该集合创建的索引有哪些:

在这里插入图片描述
建立了两个组合索引userId_1_classroomId_1_isDelete_1和userId_1_classroomId_1,

但是二者的重复度极高。(可以删掉userId_1_classroomId_1_isDelete_1,再新建一个单索引classroomId_1)

二、组合索引

1、最左匹配原则

MongoDB 中组合索引遵循最左匹配原则,即在检索数据时从复合索引的最左边开始匹配。

举例来说,上文的组合索引userId_1_classroomId_1,对于查询条件 {“userId”: “xxx”, “classroomId”: “xxx”} 可以匹配该组合索引,因为查询条件包含了索引的最左前缀;而对于查询条件 {“classroomId”:“xxx”} 则无法匹配该组合索引。

但是对查询条件 {“userId”: “xxx”} 则可以匹配该组合索引。

所以,我们需要再新建一个单索引classroomId_1。

2、ESR规则

ESR(Equality, Sort, Range)规则是创建高效组合索引的一个重要原则

  • Equality(相等):将需要精确匹配的字段放在索引的前面。这些字段用于过滤数据,减少需要扫描的文档数量。例如查询 db.xxx.find({“classroomId”: “GM03DI890”}) 中,classroomId 字段是精确匹配,应放在索引的前面。
  • Sort(排序):排序操作应放在精确匹配字段之后。因为精确匹配可以减少需要排序的文档数量,且这样可以让 MongoDB 进行非阻塞排序。例如查询 db.xxx.find({“classroomId”: “GM03DI890”}).sort({createdOn: 1}) 中,createdOn 字段用于排序,应放在 classroomId 字段之后。
    也就是说,组合索引的顺序应该是classroomId_1_createdOn_1
  • Range(范围):范围查询字段应放在索引的最后面。范围查询会扫描一定范围内的数据,将其放在最后可以提高查询效率。例如查询 db.xxx.find({price: {$gte: 15000}}) 中,price 字段是范围查询,应放在索引的最后。

三、OR查询

如果是OR查询呢?

还是举如下例:

{"$or": [{"auth": 1},{"totalIds": {"$in": [1002482]}}]
}

应该分别对 auth 和 totalIds 字段创建单独的索引,而不是创建一个组合索引。

db.xxx.createIndex({"auth":1}, {"name":"auth_1","background":true})
db.xxx.createIndex({"totalIds":1}, {"name":"totalIds_1","background":true})

这是因为 MongoDB 在使用 or 查询时,如果每个子句都有自己的索引,那么 MongoDB 可以分别使用这些索引来执行查询,然后合并结果。这通常比创建一个包含所有字段的复合索引更有效。

1、区分度问题

区分度低的字段-- auth 字段的值只有两个(0 和 1),区分度很低。通常情况下,区分度低的字段单独建立索引的收益较小,因为索引的目的是快速定位数据,而区分度低的字段在索引中并不能有效减少需要扫描的数据量。

但我们还得考虑另外一个因素。

2、查询频率

如果 auth 字段在查询中非常频繁地被使用,即使区分度低,建立索引也可能带来一些性能提升。例如,如果大部分查询都包含 auth 字段,那么建立索引可以减少全表扫描的次数。

对于 or 查询,MongoDB 会分别使用每个子查询的索引,然后合并结果。

因此,为 auth 和 totalIds 分别建立单独的索引是合理的。这样可以确保每个子查询都能高效地使用索引。

四、执行计划

使用 explain 分析查询计划:

可以通过 explain 方法来分析查询计划,查看是否使用了索引以及索引的使用情况。

{"planSummary":"IXSCAN { totalIds: 1 }, IXSCAN { auth: 1 }"}
db.xxx.find({"$or":[{"auth":1},{"totalIds":{"$in":[636622]}}]}).explain("executionStats")

在这里插入图片描述

从执行计划可以看到,现在的OR查询能够使用到这两个单独索引。

五、总结

本文在OR查询中使用了分别创建两个单独索引来提高查询效率。

这里有一个问题,auth字段的区分度低,而totalIds字段的区分度高。

在索引及文档扫描的时候,整个查询的效率是取决于auth字段,尽管totalIds查询速度快。

在这里插入图片描述
上述OR查询语句,索引扫描行数以及文档扫描行数均为18000多。

在这里插入图片描述

从中也可以看出,区分度高和低,影响的检索效率高低。

改进:

  • 从业务角度,考虑将auth查询与非auth查询分开来查询,这样就不会有OR查询场景
  • 数据库的角度,可以考虑分两张表,当要查询auth=0还是1的时候,从小表查询;如果没有auth查询,就可以使用totalIds字段,由于其区分度高,可以大大提高检索效率。

http://www.ppmy.cn/embedded/153903.html

相关文章

2. Doris数据导入与导出

一. Doris数据导入 导入方式使用场景支持的文件格式导入模式Stream Load导入本地文件或者应用程序写入csv、json、parquet、orc同步Broker Load从对象存储、HDFS等导入csv、json、parquet、orc异步Routine Load从kakfa实时导入csv、json异步 1. Stream Load 基本原理 在使用…

英语互助小程序springboot+论文源码调试讲解

第2章 开发环境与技术 英语互助小程序的编码实现需要搭建一定的环境和使用相应的技术,接下来的内容就是对英语互助小程序用到的技术和工具进行介绍。 2.1 MYSQL数据库 本课题所开发的应用程序在数据操作方面是不可预知的,是经常变动的,没有…

No. 31 笔记 | Web安全-SQL手工注入技术学习 Part 2

一、研究背景 背景介绍 SQL注入是一种常见且高危的Web安全漏洞。攻击者可以通过构造恶意SQL查询语句来绕过验证机制,执行未授权操作,如获取敏感信息、篡改数据库内容甚至控制服务器。 研究内容 本笔记探讨以下数据库的手工注入技术: MySQLAc…

PostgreSQL 超级管理员详解

1. 什么是 PostgreSQL 超级管理员 PostgreSQL 超级管理员(superuser)是拥有数据库系统最高权限的用户。他们可以执行任何数据库操作,包括但不限于创建和删除数据库、用户、表空间、模式等。超级管理员权限是 PostgreSQL 中权限的最高级别。 …

实现Windows云服务器文件共享

实现Windows云服务器文件共享 操作前提确认服务器配置修改网络和共享中心配置文件共享 操作前提 需要实现文件共享的两台云服务器都在同一子网下,且网络互通 确认服务器配置 确保“Tcp/IP NetBIOS Helper”服务状态为“已启动”: 右键“win”键&#…

基于Android的嵌入式车载导航系统(源码+lw+部署文档+讲解),源码可白嫖!

摘要 嵌入式车载导航系统设计的目的是为用户提供导航公告、地图,进行导航的一个导航系统APP。 与PC端应用程序相比,嵌入式车载导航系统的设计主要面向于广大用户,旨在为用户提供一个嵌入式车载导航系统平台。用户可以通过APP查看导航公告&am…

Qt天气预报系统获取天气数据

Qt天气预报系统获取天气数据 1、获取天气数据1.1添加天气类头文件1.2定义今天和未来几天天气数据类1.3定义一个解析JSON数据的函数1.4在mainwindow中添加weatherData.h1.5创建今天天气数据和未来几天天气数据对象1.6添加parseJson定义1.7把解析JSON数据添加进去1.8添加错误1.9解…

maven 项目怎么指定打包后名字

在 Spring Boot 的 Maven 项目中,你可以通过配置 pom.xml 文件来指定打包后的文件名。具体步骤如下: 打开 pom.xml 文件:找到你的项目根目录下的 pom.xml 文件。 配置 finalName 属性:在 标签下,添加 属性来指定打包后…