详解MongoDB排序时内存大小限制与创建索引的注意事项


Posted in MongoDB onMay 06, 2022

线上服务的MongoDB中有一个很大的表,我查询时使用了sort()根据某个字段进行排序,结果报了下面这个错误:

[Error] Executor error during find command :: caused by :: Sort operation used more than the maximum 33554432 bytes of RAM. Add an index, or specify a smaller limit.
at line 0, column 0

这是个非常常见的MongoDB报错了。因为MongoDB处理排序时,如果排序的字段没有建立索引,会把全表都丢到内存中处理。

If MongoDB cannot use an index or indexes to obtain the sort order, MongoDB must perform a blocking sort operation on the data. A blocking sort indicates that MongoDB must consume and process all input documents to the sort before returning results.

而内存的大小并不是无限使用的,MongoDB的默认设置是32MB。一旦数据量超过32MB,则会报错。

参数internalQueryExecMaxBlockingSortBytes

32MB这个限制是在参数internalQueryExecMaxBlockingSortBytes中控制。你可以在MongoDB的客户端上直接查看这个参数的值,执行以下语句:

db.runCommand({
    getParameter: 1,
    "internalQueryExecMaxBlockingSortBytes": 1
})

返回如下结果:

// 1
{
    "internalQueryExecMaxBlockingSortBytes": NumberInt("33554432"),
    "ok": 1,
    "operationTime": Timestamp(1651142670, 1),
    "$clusterTime": {
        "clusterTime": Timestamp(1651142670, 1),
        "signature": {
            "hash": BinData(0, "X09M2FBji5f+FOwaK/nLTv4+Ybs="),
            "keyId": NumberLong("7080087363631710209")
        }
    }
}

所以解决排序时内存使用超过32MB的问题,有两个方法:

给排序的字段加索引。具体怎么加索引,会在后面细讲。

修改internalQueryExecMaxBlockingSortBytes参数的大小,使用命令如下:

db.adminCommand({
    setParameter: 1,
    internalQueryExecMaxBlockingSortBytes: 104857600
})

MongoDB 4.3的internalQueryMaxBlockingSortMemoryUsageBytes

我准备在本地的MongoDB上复现这个问题,于是把这个表直接导入到本地MongoDB中。结果发现排序时并没有报错。使用上面的命令查看internalQueryExecMaxBlockingSortBytes参数的值时,返回如下结果:

[17][ProtocolError] no option found to get

Google了一下,发现了MongoDB的官方网站上的两个相关JIRA。

第一个JIRA [SERVER-44053] Rename setParameter for maximum memory usage of blocking sort - MongoDB Jira里表示,在4.3.1版本时,因为参数命名描述不清楚,所以将参数internalQueryExecMaxBlockingSortBytes改为了internalQueryMaxBlockingSortMemoryUsageBytes。这解释了为什么我执行查询参数的语句时,没有返回结果。

第二个JIRA [SERVER-50767] internalQueryExecMaxBlockingSortBytes causing config exception on mongod load - Mongo中,Comments里提到了,新的internalQueryMaxBlockingSortMemoryUsageBytes参数,默认值从32MB改成了100MB。也许我的这个表使用100MB内存进行排序就够用了,所以没有报错。

详解MongoDB排序时内存大小限制与创建索引的注意事项

所以在4.3以上的版本(本机是5.0.4),执行以下命令:

db.runCommand({
    getParameter: 1,
    "internalQueryMaxBlockingSortMemoryUsageBytes": 1
})

可以看到查询结果:

{
    "internalQueryMaxBlockingSortMemoryUsageBytes": NumberInt("104857600"),
    "ok": 1
}

而服务器上的MongoDB版本为4.0.3,因此是爆出来最上面的问题。

排序字段如何加索引?

这是个很简单的问题,你用哪个字段排序,就对哪个字段加索引就好了。比如我要根据A字段进行排序,则增加A字段的索引。

-- 加索引
db.bigMongoTable.createIndex({
    "A": 1
});
-- 查询
db.bigMongoTable.find({}).sort({
    "A": 1
});

但是如果我改主意了,我要根据A、B两个字段做排序:

db.bigMongoTable.find({}).sort({
    "A": 1,
    "B": 1
});

那么熟悉的报错就又回来了。

是的!机智的MongoDB并不会像我们想的那样,先用上A的索引,从而省点力气。他依旧会把全部的数据丢到内存里排序……

那我再加个B字段的索引吧,毕竟在MongoDB查询的时候,对两个字段分别建单键索引,灵活性比直接建一个复合索引要好一些,而且MongoDB的索引交集也可以让这两个单键索引实现和复合索引一样的效果。

哦,不行哟,还是那个报错。

所以,当多字段排序时,你必须要建一个包含了这些字段的复合索引,且要注意以下几点:

  • 查询时参与排序的多个字段的顺序,要和创建的索引每个字段的顺序保持一致。比如你创建的索引是:db.bigMongoTable.createIndex({"A":1,"B":1,"C":1});那么你的排序语句也要按照顺序如下:sort({"A":1,"B":1,"C":1})。如果你调换A和B的顺序,如下:sort({"B":1,"A":1,"C":1}),则索引不会生效。
  • 参与查询的字段少于索引的字段,则要保证符合前缀匹配。还是第一点里的索引,如果排序语句是这样:sort({"A":1,"B":1}),则索引继续生效。如果是这样:sort({"A":1,"C":1}),则无法生效。这个你可以理解成和MySQL类似,索引都是按照最左匹配规则去触发的,一条索引的中间部分跳过了就无效了。
  • 参与sort的字段的排序方式,要和创建索引时的排序方式保持完全一致,或者完全相反。对于第一点里的索引,如果查询sort({"A":-1,"B":1})或者sort({"A":1,"B":-1}),索引则不会生效。只有在查询sort({"A":1,"B":1})或者sort({"A":-1,"B":-1})时,索引才会生效。

总结

  • MongoDB的查询结果在进行排序时,如果排序字段没有添加索引,会将数据全部放到内存中计算。如果数据量过大,超过配置的内存大小,则会报错。
  • 4.3版本之前,使用内存的最大值通过参数internalQueryExecMaxBlockingSortBytes控制,默认为32MB。4.3版本之后,通过参数internalQueryMaxBlockingSortMemoryUsageBytes控制。
  • 正常的解决方式是添加索引,但是索引要包括全部参与排序的字段,且要遵循前缀匹配策略。

到此这篇关于详解MongoDB排序时内存大小限制与创建索引的注意事项的文章就介绍到这了!


Tags in this post...

MongoDB 相关文章推荐
MongoDB balancer的使用详解
Apr 30 MongoDB
浅析MongoDB之安全认证
Jun 26 MongoDB
mongodb的安装和开机自启动详细讲解
Aug 02 MongoDB
mongodb清除连接和日志的正确方法分享
Sep 15 MongoDB
centos8安装MongoDB的详细过程
Oct 24 MongoDB
MongoDB连接数据库并创建数据等使用方法
Nov 27 MongoDB
SpringBoot系列之MongoDB Aggregations用法详解
Feb 12 MongoDB
剖析后OpLog订阅MongoDB的数据变更就没那么难了
Feb 24 MongoDB
一次线上mongo慢查询问题排查处理记录
Mar 18 MongoDB
mongoDB数据库索引快速入门指南
Mar 23 MongoDB
MongoDB修改oplog大小的四种方法
Apr 11 MongoDB
NoSQL优缺点与MongoDB数据库简介
Jun 05 MongoDB
MongoDB数据库之添删改查
Mongodb 迁移数据块的流程介绍分析
SpringBoot集成MongoDB实现文件上传的步骤
Apr 18 #MongoDB
Centos系统通过Docker安装并搭建MongoDB数据库
MongoDB修改oplog大小的四种方法
Apr 11 #MongoDB
MongoDB支持的索引类型
Apr 11 #MongoDB
MongoDB支持的数据类型
Apr 11 #MongoDB
You might like
php中如何使对象可以像数组一样进行foreach循环
2013/08/09 PHP
php可应用于面包屑导航的迭代寻找家谱树实现方法
2015/02/02 PHP
symfony2.4的twig中date用法分析
2016/03/18 PHP
PHP入门教程之数组用法汇总(创建,删除,遍历,排序等)
2016/09/11 PHP
浅析php-fpm静态和动态执行方式的比较
2016/11/09 PHP
一个很简单的办法实现TD的加亮效果.
2006/06/29 Javascript
JavaScript 快捷键设置实现代码
2009/03/13 Javascript
一步一步教你写一个jQuery的插件教程(Plugin)
2009/09/03 Javascript
ASP.NET jQuery 实例13 原创jQuery文本框字符限制插件-TextArea Counter
2012/02/03 Javascript
Extjs显示从数据库取出时间转换JSON后的出现问题
2012/11/20 Javascript
详解Javascript ES6中的箭头函数(Arrow Functions)
2016/08/24 Javascript
很棒的一组js图片轮播特效
2017/01/12 Javascript
nodejs制作爬虫实现批量下载图片
2017/05/19 NodeJs
Windows下快速搭建NodeJS本地服务器的步骤
2017/08/09 NodeJs
通过jquery toggleClass()属性制作文章段落更改背景颜色
2018/05/21 jQuery
在vue中使用Autoprefixed的方法
2018/07/27 Javascript
[02:48]DOTA2超级联赛专访海涛:你们的选择没有错
2013/06/07 DOTA
[52:07]完美世界DOTA2联赛PWL S3 LBZS vs access 第二场 12.10
2020/12/13 DOTA
Python3 入门教程 简单但比较不错
2009/11/29 Python
利用Python绘制数据的瀑布图的教程
2015/04/07 Python
使用PM2+nginx部署python项目的方法示例
2018/11/07 Python
selenium+python自动化测试之页面元素定位
2019/01/23 Python
django基于cors解决跨域请求问题详解
2019/08/06 Python
python爬取Ajax动态加载网页过程解析
2019/09/05 Python
简单了解python元组tuple相关原理
2019/12/02 Python
Django实现whoosh搜索引擎使用jieba分词
2020/04/08 Python
解决python 虚拟环境删除包无法加载的问题
2020/07/13 Python
Stuart Weitzman美国官网:美国奢华鞋履品牌
2016/08/18 全球购物
机械系大学毕业生推荐信
2013/11/27 职场文书
编辑找工作求职信范文
2013/12/16 职场文书
乒乓球兴趣小组活动总结
2014/07/08 职场文书
2014高中生入党思想汇报范文
2014/09/13 职场文书
初中作文评语
2014/12/25 职场文书
创业计划书之烤红薯
2019/09/26 职场文书
mysql多表查询-笔记七
2021/04/05 MySQL
clear 万能清除浮动(clearfix:after)
2023/05/21 HTML / CSS