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

目录
  • 参数internalQueryExecMaxBlockingSortBytes
  • MongoDB 4.3的internalQueryMaxBlockingSortMemoryUsageBytes
  • 排序字段如何加索引?
  • 总结

线上服务的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内存进行排序就够用了,所以没有报错。

所以在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排序时内存大小限制与创建索引注意事项的文章就介绍到这了,更多相关MongoDB排序内存限制内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • cgroup限制mongodb进程内存大小

    以限制mongodb的内存大小为例. mkdir /cgroup/memory/test/ echo 50M > /cgroup/memory/test/memory.limit_in_bytes echo 50M > /cgroup/memory/test/memory.memsw.limit_in_bytes cgexec -g memory:test mongod -port 27017 --bind_ip 127.0.0.1 --dbpath /var/lib/mongo 通过cgro

  • 解决MongoDB 排序超过内存限制的问题

    对集合执行一个大排序操作(如聚合),出现以下错误:(测试版本:MongoDB 3.0.6) > db.bigdata.aggregate( {$group : {_id : "$range", total : { $sum : 1 }}}, {$sort : {total : -1}} ); #... aggregate failed at Error (<anonymous>) at doassert (src/mongo/shell/assert.js:11:14

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

    目录 参数internalQueryExecMaxBlockingSortBytes MongoDB 4.3的internalQueryMaxBlockingSortMemoryUsageBytes 排序字段如何加索引? 总结 线上服务的MongoDB中有一个很大的表,我查询时使用了sort()根据某个字段进行排序,结果报了下面这个错误: [Error] Executor error during find command :: caused by :: Sort operation used

  • 优化Tomcat配置(内存、并发、缓存等方面)方法详解

    Tomcat有很多方面,我从内存.并发.缓存等方面介绍优化方法. 一.Tomcat内存优化 Tomcat内存优化主要是对 tomcat 启动参数优化,我们可以在 tomcat 的启动脚本 catalina.sh 中设置 java_OPTS 参数. JAVA_OPTS参数说明 server 启用jdk 的 server 版: -Xms java虚拟机初始化时的最小内存: -Xmx java虚拟机可使用的最大内存: -XX: PermSize 内存永久保留区域 -XX:MaxPermSize 内存最

  • C++动态内存分配(new/new[]和delete/delete[])详解

    C++动态内存分配(new/new[]和delete/delete[])详解 为了解决这个普通的编程问题,在运行时能创建和销毁对象是基本的要求.当然,C已提供了动态内存分配函数malloc( )和free( ),以及malloc( )的变种(realloc:改变分配内存的大小,calloc:指针指向内存前初始化),这些函数在运行时从堆中(也称自由内存)分配存储单元,但是运用这些库函数需要计算需要开辟内存的大小,容易出现错误. 那么通常我们在C语言中我们开辟内存的方式如下: (void*)mall

  • IOS 创建并发线程的实例详解

    IOS 创建并发线程的实例详解 创建并发线程 主线程一般都是处理UI界面及用户交互的事儿的.其他的事一般就要另外的线程去处理,如下载,计算等... 现在先简单创建3个线程,分别打印出1-1000,,为了方便,线程3就放在主线程中执行. - (void) firstCounter{ @autoreleasepool { NSUInteger counter = 0; for (counter = 0; counter < 1000; counter++){ NSLog(@"First Cou

  • 利用Python创建位置生成器的示例详解

    目录 介绍 开始 步骤 创建训练数据集 创建测试数据集 将合成图像转换回坐标 放在一起 结论 介绍 在这篇文章中,我们将探索如何在美国各地城市的地图数据和公共电动自行车订阅源上训练一个快速生成的对抗网络(GAN)模型. 然后,我们可以通过为包括东京在内的世界各地城市创建合成数据集来测试该模型的学习和概括能力. git clone https://github.com/gretelai/GAN-location-generator.git 在之前的一篇博客中,我们根据电子自行车订阅源中的精确位置数

  • Git 创建分支提交远程分支详解

    Git 创建分支提交远程分支详解 1.创建本地分支 git branch 分支名,例如:git branch 2.0.1.20120806 注:2.0.1.20120806是分支名称,可以随便定义. 2.切换本地分支 git checkout 分支名,例如从master切换到分支:git checkout 2.0.1.20120806 3.远程分支就是本地分支push到服务器上.比如master就是一个最典型的远程分支(默认). git push origin 2.0.1.20120806 4.

  • navicat创建MySql定时任务的方法详解

    navicat创建MySql 定时任务详解 事件(event)是MySQL在相应的时刻调用的过程式数据库对象.一个事件可调用一次,也可周期性的启动,它由一个特定的线程来管理的,也就是所谓的"事件调度器".事件和触发器类似,都是在某些事情发生的时候启动.当数据库上启动一条语句的时候,触发器就启动了,而事件是根据调度事件来启动的.由于他们彼此相似,所以事件也称为临时性触发器.事件取代了原先只能由操作系统的计划任务来执行的工作,而且MySQL的事件调度器可以精确到每秒钟执行一个任务,而操作系

  • PHP创建简单RPC服务案例详解

    RPC 定义 RPC(Remote Procedure Call)即远程过程调用,指被调用方法的具体实现不在程序运行本地,而是在别的某个地方.主要应用于不同的系统之间的远程通信和相互调用. 如 A 调用 B 提供的 remoteAdd 方法: 首先A与B之间建立一个TCP连接: 然后A把需要调用的方法名(这里是remoteAdd)以及方法参数(10, 20)序列化成字节流发送出去: B接受A发送过来的字节流,然后反序列化得到目标方法名,方法参数,接着执行相应的方法调用(可能是localAdd)并

  • C# .NET创建虚拟目录的方法详解

    目录 使用背景 配置 创建 使用 结语 使用背景 虚拟目录(virtual directory),计算机术语,每个 Internet服务可以从多个目录中发布.通过以通用命名约定 (UNC) 名.用户名及用于访问权限的密码指定目录,可将每个目录定位在本地驱动器或网络上.指定客户 URL地址, 服务将整个发布目录集提交给客户作为一个目录树.宿主目录是“虚拟”目录树的根.虚拟目录的实际子目录对于客户也是可用的.只有http://www.服务支持虚拟服务器:而 FTP和 gopher服务则只能有一个宿主

随机推荐