日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

mongodb 排序_技术分享 | MongoDB 一次排序超过内存限制的排查

發(fā)布時(shí)間:2025/3/11 编程问答 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 mongodb 排序_技术分享 | MongoDB 一次排序超过内存限制的排查 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

本文目錄:

一、背景

1. 配置參數(shù)檢查

2. 排序字段是否存在索引

二、測(cè)試環(huán)境模擬索引對(duì)排序的影響

1. 測(cè)試環(huán)境信息

2. 報(bào)錯(cuò)語句的執(zhí)行計(jì)劃解釋 3. 建立新的組合索引進(jìn)行測(cè)試

三、引申的組合索引問題

1. 查詢語句中,排序字段 _id 使用降序

2. 查詢語句中,排序字段 Num 和 _id 全部使用降序

四、引申的聚合查詢問題

1.Sort stage 使用內(nèi)存排序

五、結(jié)論

1. 排序內(nèi)存限制的問題

2. 使排序操作使用到索引?

1) 為查詢語句創(chuàng)建合適的索引

2) 注意前綴索引的使用

3.聚合查詢添加allowDiskUse選項(xiàng)

六、參考文獻(xiàn)

一、背景

某次在客戶現(xiàn)場(chǎng)處理一起APP業(yè)務(wù)中頁面訪問異常的問題,該頁面直接是返回一行行碩大的報(bào)錯(cuò)代碼,錯(cuò)誤大概如下所示:

MongoDB.Driver.MongoQueryException: QueryFailure flag was Executor error: OperationFailed: Sort operation used more than the maximum 33554432 bytes of RAM. Add an index, or specify a smaller limit

報(bào)錯(cuò)頁面很明顯告知了問題排查的方向:

  • Sort operation 該頁面涉及的MongoDB查詢語句使用了排序。

  • more than the maximum 33554432 排序操作超過了MongoDB單個(gè)Session排序可使用的最大內(nèi)存限制。

檢索MongoDB的日志確實(shí)存在大量的查詢報(bào)錯(cuò),跟APP頁面報(bào)錯(cuò)能夠?qū)?yīng)上;并且日志中排序使用的字段為 DT 和 _id ,升序排序。????

涉及業(yè)務(wù)敏感字,全文會(huì)略過、改寫或使用'xxx'代替

2019-XX-XXTXX:XX:XX.XXX+0800 E QUERY [conn3644666] Plan executor error during find: FAILURE, ·········· sortPattern: {DT: 1, _id: 1 }, memUsage: 33555513, memLimit: 33554432, ·············· }

2019-XX-XXTXX:XX:XX.XXX+0800 I QUERY [conn3644666] assertion 17144 Executor error: OperationFailed: Sort operation used more than the maximum 33554432 bytes of RAM. Add an index, or specify a smaller limit. ns:XXXXX query:{ $query:········ $orderby: { DT: 1, _id: 1 }, $hint: { CID: 1, CVX: 1 } }

1. 配置參數(shù)檢查

MongoDB Server中確認(rèn)了對(duì)于Sort排序能夠支持的最大內(nèi)存限制為32M。

> use admin

switched to db admin

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

{ "internalQueryExecMaxBlockingSortBytes" : 33554432, "ok" : 1 }

2. 排序字段是否存在索引

根據(jù)報(bào)錯(cuò)信息的建議,查看官方文檔的解釋:

In MongoDB, sort operations can obtain the sort order by retrieving documents based on the ordering in an index. If the query planner cannot obtain the sort order from an index, it will sort the results in memory. Sort operations that use an index often have better performance than those that do not use an index. In addition, sort operations that do not use an index will abort when they use 32 megabytes of memory.

文檔中意思大概是:在排序字段未利用到索引的情況下,若超過32M內(nèi)存則會(huì)被Abort,語句直接返回報(bào)錯(cuò)。

那么現(xiàn)在方向基本可以鎖定在排序操作是否使用到索引了;查看該集合狀態(tài),排序字段 DT 和?_id確實(shí)存在索引 _id_、 DT_1 、 DT_1_CID_1_id_1 ,為啥還會(huì)報(bào)錯(cuò)?帶著疑問我們下文在測(cè)試環(huán)境進(jìn)行模擬。

> db.xxx.getIndexes()

[

·········

{

"v" : 1,

"key" : {

"_id" : 1

},

"name" : "_id_",

"ns" : "xxx.xxx"

},

{

"v" : 1,

"key" : {

"DT" : 1

},

"name" : "DT_1",

"ns" : "xxx.xxx"

},

{

"v" : 1,

"key" : {

"DT" : 1,

"CID" : 1,

"_id" : 1

},

"name" : "DT_1_CID_1_id_1",

"ns" : "xxx.xxx"

}

···········

二、測(cè)試環(huán)境模擬索引對(duì)排序的影響

1.測(cè)試環(huán)境信息

MongoDB版本4.0.10
MongoDB 存儲(chǔ)引擎wiredTiger
數(shù)據(jù)量1000000
測(cè)試集合名data_test

集合數(shù)據(jù)存儲(chǔ)格式

> db.data_test.findOne()

{

"_id" : ObjectId("5d0872dc5f13ad3173457186"),

"Name" : "Edison",

"Num" : 195930,

"loc" : {

"type" : "Point",

"coordinates" : [

118.0222094243601,

36.610739264097646

]

}

}

集合索引信息

> db.data_test.getIndexes()

[

{

"v" : 2,

"key" : {

"_id" : 1

},

"name" : "_id_",

"ns" : "mongobench.data_test"

},

{

"v" : 2,

"key" : {

"Name" : 1

},

"name" : "Name_1",

"ns" : "mongobench.data_test"

},

{

"v" : 2,

"key" : {

"Num" : 1

},

"name" : "Num_1",

"ns" : "mongobench.data_test"

},

{

"v" : 2,

"key" : {

"Num" : 1,

"Name" : 1,

"_id" : 1

},

"name" : "Num_1_Name_1__id_1",

"ns" : "mongobench.data_test"

}

]

查詢語句

為測(cè)試方便,將業(yè)務(wù)中報(bào)錯(cuò)的聚合查詢按同樣查詢邏輯修改為 Mongo Shell 中的普通 find() 查詢

2. 報(bào)錯(cuò)語句的執(zhí)行計(jì)劃解釋

測(cè)試查詢報(bào)錯(cuò)的語句,嘗試查看其查詢計(jì)劃如下:

> db.data_test.find({'Num':{"$gt":500000}}).sort({"Num":1,"_id":1}).explain()

2019-06-19T18:21:14.745+0800 E QUERY [js] Error: explain failed: {

"ok" : 0,

"errmsg" : "Sort operation used more than the maximum 33554432 bytes of RAM. Add an index, or specify a smaller limit.",

"code" : 96,

"codeName" : "OperationFailed"

}

直接報(bào)錯(cuò),這里有個(gè)疑問為啥連執(zhí)行計(jì)劃都看不了?先不急,我們先刪除對(duì)于排序字段的組合索引 Num_1_Name_1_id_1 后,再查看執(zhí)行計(jì)劃:

> db.data_test.dropIndex('Num_1_Name_1__id_1')

{ "nIndexesWas" : 4, "ok" : 1 }

db.data_test.find({'Num':{"$gt":500000}}).sort({"Num":1,"_id":1}).explain('executionStats')

{

"queryPlanner" : {

"plannerVersion" : 1,

"namespace" : "mongobench.data_test",

"indexFilterSet" : false,

"parsedQuery" : {

"Num" : {

"$gt" : 500000

}

},

"winningPlan" : {

"stage" : "SORT",

"sortPattern" : {

"Num" : 1,

"_id" : 1

},

"inputStage" : {

"stage" : "SORT_KEY_GENERATOR",

·······

"rejectedPlans" : [ ]

},

"executionStats" : {

"executionSuccess" : false,

"errorMessage" : "Exec error resulting in state FAILURE :: caused by :: Sort operation used more than the maximum 33554432 bytes of RAM. Add an index, or specify a smaller limit.",

"errorCode" : 96,

"nReturned" : 0,

"executionTimeMillis" : 1504,

"totalKeysExamined" : 275037,

"totalDocsExamined" : 275037,

"executionStages" : {

"stage" : "SORT",

"nReturned" : 0,

"executionTimeMillisEstimate" : 188,

····

"memUsage" : 33554514,

"memLimit" : 33554432,

"inputStage" : {

"stage" : "SORT_KEY_GENERATOR",

"nReturned" : 275037,

·····

查詢計(jì)劃中關(guān)鍵參數(shù)的解釋:

1. queryPlanner:explain中三種模式之一,默認(rèn)模式。表示不會(huì)執(zhí)行查詢語句而是選出最優(yōu)的查詢計(jì)劃即winning plan,剩余兩種模式分別是 executionStats 和 allPlansExecution

  • winningPlan:MongoDB優(yōu)化器選擇的最優(yōu)執(zhí)行計(jì)劃

[1]stage:包括COLLSCAN 全表掃描、IXSCAN 索引掃描、FETCH 根據(jù)索引去檢索指定文檔、SORT 在內(nèi)存中進(jìn)行排序(未使用索引)

[2]sortPattern:需排序的字段

[3]inputStage:winningPlan.stage的子階段

  • rejectedPlans:優(yōu)化器棄用的執(zhí)行計(jì)劃

2. executionStats:返回執(zhí)行結(jié)果的狀態(tài),如語句成功或失敗等

  • executionSuccess:語句執(zhí)行是否成功

  • errorMessage:錯(cuò)誤信息

  • nReturned:返回的記錄數(shù)

  • totalKeysExamined:索引掃描總行數(shù)

  • totalDocsExamined:文檔掃描總行數(shù)

  • memUsage:Sort 使用內(nèi)存排序操作使用的內(nèi)存大小

  • memLimit:MongoDB 內(nèi)部限制Sort操作的最大內(nèi)存

上述執(zhí)行計(jì)劃表明查詢語句在未使用索引排序的情況下如果排序使用的內(nèi)存超過32M必定會(huì)報(bào)錯(cuò),那么為什么沒有使用到索引排序,是不是跟組合索引的順序有關(guān)?

3. 建立新的組合索引進(jìn)行測(cè)試

直接創(chuàng)建 Num 和 _id 列都為升序的組合索引,再次查看執(zhí)行計(jì)劃:

> db.data_test.ensureIndex({Num:1,_id:1})

{

"createdCollectionAutomatically" : false,

"numIndexesBefore" : 3,

"numIndexesAfter" : 4,

"ok" : 1

}

> db.data_test.find({'Num':{"$gt":500000}}).sort({"Num":1,"_id":1}).explain('executionStats')

{

"queryPlanner" : {

"plannerVersion" : 1,

"namespace" : "mongobench.data_test",

"indexFilterSet" : false,

"parsedQuery" : {

"Num" : {

"$gt" : 500000

}

},

"winningPlan" : {

"stage" : "FETCH",

"inputStage" : {

"stage" : "IXSCAN",

"keyPattern" : {

"Num" : 1,

"_id" : 1

},

"indexName" : "Num_1__id_1",

·········

"rejectedPlans" : [

{

"stage" : "SORT",

"sortPattern" : {

"Num" : 1,

"_id" : 1

},

"inputStage" : {

"stage" : "SORT_KEY_GENERATOR",

·········

"executionStats" : {

"executionSuccess" : true,

"nReturned" : 499167,

"executionTimeMillis" : 1355,

"totalKeysExamined" : 499167,

"totalDocsExamined" : 499167,

"executionStages" : {

"stage" : "FETCH",

"nReturned" : 499167,

"executionTimeMillisEstimate" : 102,

"works" : 499168,

"advanced" : 499167,

"needTime" : 0,

"needYield" : 0,

"saveState" : 3901,

"restoreState" : 3901,

"isEOF" : 1,

"invalidates" : 0,

"docsExamined" : 499167,

"alreadyHasObj" : 0,

"inputStage" : {

"stage" : "IXSCAN",

"nReturned" : 499167,

"executionTimeMillisEstimate" : 14,

"works" : 499168,

·······

上述執(zhí)行計(jì)劃說明:

  • winningPlan.stage:優(yōu)化器選擇了FETCH+IXSCAN的Stage,而不是之前的Sort;這是最優(yōu)的方式之一,也就是通過索引檢索指定的文檔數(shù)據(jù),并在索引中完成排序 ("keyPattern" : {"Num" : 1,"_id" : 1}) ,效率最高

  • rejectedPlans:Sort 使用內(nèi)存排序的方式被優(yōu)化器棄用

  • executionSuccess:語句執(zhí)行成功

  • nReturned:語句返回結(jié)果數(shù)為499167

三、引申的組合索引問題

上文中查詢語句explain()直接報(bào)錯(cuò),是因?yàn)榻M合索引為{Num_1_Name_1_id_1},而查詢語句為sort({"Num":1,"_id":1}),未遵循最左原則,索引無法被使用到而后優(yōu)化器選擇Sort Stage觸發(fā)了內(nèi)存限制并Abort。

至于為啥MongoDB連執(zhí)行計(jì)劃都不返回給你,可以后續(xù)再討論,歡迎評(píng)論

創(chuàng)建合適的組合索引后,查詢語句成功執(zhí)行;那么如果不按照索引的升降順序執(zhí)行語句會(huì)怎樣?

1.查詢語句中,排序字段 _id 使用降序

當(dāng)前的組合索引為{"key" : {"Num" : 1, "_id" : 1} },也就是都為升序,而我們將查詢語句中排序字段 _id 使用降序排序時(shí),查詢語句直接報(bào)錯(cuò),說明該語句也未使用到索引排序,而是使用的Sort Stage。

> db.data_test.find({'Num':{"$gt":500000}}).sort({"Num":1,"_id":-1}).explain('executionStats')

2019-06-19T19:32:30.939+0800 E QUERY [js] Error: explain failed: {

"ok" : 0,

"errmsg" : "Sort operation used more than the maximum 33554432 bytes of RAM. Add an index, or specify a smaller limit.",

"code" : 96,

"codeName" : "OperationFailed"

}

2.查詢語句中,排序字段 Num _id 全部使用降序

我們現(xiàn)在將查詢語句的排序字段全部使用降序,與組合索引全部相反再測(cè)試,執(zhí)行成功。

> db.data_test.find({'Num':{"$gt":500000}}).sort({"Num":-1,"_id":-1}).explain('executionStats')

{

"queryPlanner" : {

······

"winningPlan" : {

"stage" : "FETCH",

"inputStage" : {

"stage" : "IXSCAN",

"keyPattern" : {

"Num" : 1,

"_id" : 1

},

"indexName" : "Num_1__id_1",

·······

"rejectedPlans" : [

{

"stage" : "SORT",

·······

"executionStats" : {

"executionSuccess" : true,

·······

"inputStage" : {

"stage" : "IXSCAN",

·······

"indexName" : "Num_1__id_1",

······

"ok" : 1

}

再次做其他查詢組合測(cè)試 sort({"Num":-1,"_id":1}),執(zhí)行依然失敗;說明只有在排序列的升降序只有和組合索引中的 方向 保持 全部相同 全部相反,語句執(zhí)行才能成功。

四、引申的聚合查詢問題

上文中的查詢測(cè)試語句是在 MongoDB Shell 執(zhí)行的 find() 查詢方法,但是業(yè)務(wù)程序中查詢一般都是使用聚合查詢方法 aggregate(),對(duì)于聚合查詢中的Sort Stage,官方文檔說明了使用內(nèi)存排序能使用最大的內(nèi)存為 100M,若需要避免報(bào)錯(cuò)則需要添加 {allowDiskUse : true} 參數(shù)。

The $sort stage has a limit of 100 megabytes of RAM. By default, if the stage exceeds this limit, $sort will produce an error. To allow for the handling of large datasets, set the allowDiskUse option to true to enable $sort operations to write to temporary files. See the allowDiskUse option in db.collection.aggregate() method and the aggregate command for details.

1.Sort stage 使用內(nèi)存排序

將普通的 find() 方法轉(zhuǎn)為 aggregate() 聚合方法,語義不變,特意將排序字段 _id 修改為 降序 -1 ,那么查詢計(jì)劃將無法使用到組合索引只能使用Sort stage。下文中查詢依然報(bào)錯(cuò),Sort stage操作使用的內(nèi)存超過100M

> db.data_test.explain('executionStats').aggregate([{ $match : { Num : { $gt : 500000} } },{ $sort : { "Num" : 1, _id: -1 } }])

2019-06-19T20:28:43.859+0800 E QUERY [js] Error: explain failed: {

"ok" : 0,

"errmsg" : "Sort exceeded memory limit of 104857600 bytes, but did not opt in to external sorting. Aborting operation. Pass allowDiskUse:true to opt in.",

"code" : 16819,

"codeName" : "Location16819"

} :

_getErrorWithCode@src/mongo/shell/utils.js:25:13

throwOrReturn@src/mongo/shell/explainable.js:31:1

constructor/this.aggregate@src/mongo/shell/explainable.js:121:1

@(shell):1:1

添加 {allowDiskUse: true} 參數(shù),可以使Sort stage操作繞過內(nèi)存限制而使用磁盤,查詢語句可以執(zhí)行成功:

> db.data_test.explain('executionStats').aggregate([{ $match : { Num : { $gt : 500000} } },{ $sort : { "Num" : 1, _id: -1 } }],{allowDiskUse: true})

{

"stages" : [

······

"executionStats" : {

"executionSuccess" : true,

"nReturned" : 499167,

"executionTimeMillis" : 4128,

"totalKeysExamined" : 499167,

"totalDocsExamined" : 499167,

······

{

"$sort" : {

"sortKey" : {

"Num" : 1,

"_id" : -1

}

}

}

],

"ok" : 1

}

五、結(jié)論

1.排序內(nèi)存限制的問題

MongoDB使用內(nèi)存進(jìn)行排序的場(chǎng)景只有是Sort stage,官方文檔有說明:

If MongoDB can use an index scan to obtain the requested sort order, the result will not include a SORT stage. Otherwise, if MongoDB cannot use the index to sort, the explain result will include a SORT stage.

意思大概是如果MongoDB可以使用索引掃描來進(jìn)行排序,那么結(jié)果將不包括SORT stage。否則如果MongoDB無法使用索引進(jìn)行排序,那么查詢計(jì)劃將包括SORT stage。

使用索引掃描的效率是遠(yuǎn)大于直接將結(jié)果集放在內(nèi)存排序的,所以MongoDB為了使查詢語句更有效率的執(zhí)行,限制了 排序內(nèi)存的使用,因而規(guī)定了只能使用 32M,該種考慮是非常合理的。

但也可通過手工調(diào)整參數(shù)進(jìn)行修改(不建議):

# 比如調(diào)大到 128M

## 在線調(diào)整

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

## 持久到配置文件

setParameter:

internalQueryExecMaxBlockingSortBytes: 134217728

2.使排序操作使用到索引

1)為查詢語句創(chuàng)建合適的索引如果查詢中排序是單列排序,如sort({"Num":1}),那么只需添加為 Num 列添加索引即可,排序的順序無影響

## 例如索引為 {'Num':1},查詢不管升/降序都可使用到索引排序

db.data_test.find().sort({Num:1})

db.data_test.find().sort({Num:-1})

如果查詢中排序是使用組合排序,如sort({"Num":1,"id":1}),那么需要建立對(duì)應(yīng)的組合索引,如{"key" : {"Num" : 1, "_id" : 1} 或者 {"key" : {"Num" : -1, "_id" : -1}

## 例如索引為{"Num" : 1, "_id" : 1},可以用到索引排序的場(chǎng)景為

db.data_test.find().sort({Num:1,_id:1})

db.data_test.find().sort({Num:-1,_id:-1})

注意保持查詢中組合排序的升降序和組合索引中的 方向 保持 全部相同 或 全部相反

2)注意前綴索引的使用

上文查詢報(bào)錯(cuò)的案例分析已說明了組合索引每一個(gè)鍵的順序非常重要,這將決定該組合索引在查詢過程中能否被使用到,也將是MongoDB的索引及排序同樣需遵循最左前綴原則。

3. 聚合查詢添加allowDiskUse選項(xiàng)

盡可能的保證查詢語句的排序能夠使用索引排序,但如果業(yè)務(wù)需要規(guī)避排序內(nèi)存限制報(bào)錯(cuò)的問題,那么需要在代碼中添加 {allowDiskUse : true} 參數(shù)。

六、參考文獻(xiàn)

https://docs.mongodb.com/manual/tutorial/sort-results-with-indexes/index.html

https://docs.mongodb.com/manual/reference/operator/aggregation/sort/#sort-memory-limit

https://docs.mongodb.com/manual/reference/explain-results/#executionstats

近期社區(qū)動(dòng)態(tài)

第三期?社區(qū)技術(shù)內(nèi)容征稿?

所有稿件,一經(jīng)采用,均會(huì)為作者署名。

征稿主題:MySQL、分布式中間件DBLE、數(shù)據(jù)傳輸組件DTLE相關(guān)的技術(shù)內(nèi)容

活動(dòng)時(shí)間:2019年6月11日?-?7月11日

本期投稿獎(jiǎng)勵(lì)

投稿成功:京東卡200元*1

優(yōu)秀稿件:京東卡200元*1+社區(qū)定制周邊(包含:定制文化衫、定制傘、鼠標(biāo)墊)

優(yōu)秀稿件評(píng)選,文章獲得“好看數(shù)量排名前三的稿件為本期優(yōu)秀稿件。

喜歡點(diǎn)分享”,不行就看”

多喝熱水,重啟試試

總結(jié)

以上是生活随笔為你收集整理的mongodb 排序_技术分享 | MongoDB 一次排序超过内存限制的排查的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。