您的位置:首页 > 编程语言 > Go语言

mongo3.2 数组索引作为联合索引的一部分导致的范围查询问题

2017-07-23 12:03 381 查看

摘要

mongo索引一文中有介绍过mongo数组索引,这边主要介绍在mongo3.2中遇到的一个将array字段作为一个联合索引的字段,然后进行非array字段的范围查询遇到的问题。

问题

post collection

{
"_id" : ObjectId("5972c7b0a98a215bd51b9f9a"),
"son" : [
1,
2
],
"age" : 1,
"name" : "stone",
"created" : ISODate("2017-07-23T03:42:20.201Z")
}


索引son_age:

{son:1,age:1}


范围查询
db.getCollection('post').find({son:1,age:{$gt:1,$lt:30}}).explain("executionStats")


"inputStage" : {
"stage" : "IXSCAN",
"nReturned" : 1,
"executionTimeMillisEstimate" : 0,
"works" : 2,
"advanced" : 1,
"needTime" : 0,
"needYield" : 0,
"saveState" : 0,
"restoreState" : 0,
"isEOF" : 1,
"invalidates" : 0,
"keyPattern" : {
"son" : 1,
"age" : 1
},
"indexName" : "son_age",
"isMultiKey" : true,
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 1,
"direction" : "forward",
"indexBounds" : {
"son" : [
"[1.0, 1.0]"
],
"age" : [
"[-1.#INF, 30.0)"
]
},


但是发现index bound并没有如预想的那样两边都取到,而只取到了上限,然后做filter。这样当数据量多的时候,扫描的index就多了很多,必然会导致性能问题。之前在线上就遇到这样的问题,按照日期进行刷选,查询很慢。

分析

首先在mongo3.4中执行了一把,看到不一样的结果

"inputStage" : {
"stage" : "IXSCAN",
"nReturned" : 0,
"executionTimeMillisEstimate" : 0,
"works" : 1,
"advanced" : 0,
"needTime" : 0,
"needYield" : 0,
"saveState" : 0,
"restoreState" : 0,
"isEOF" : 1,
"invalidates" : 0,
"keyPattern" : {
"son" : 1,
"age" : 1
},
"indexName" : "son_age",
"isMultiKey" : true,
"multiKeyPaths" : {
"son" : [
"son"
],
"age" : []
},
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 1,
"direction" : "forward",
"indexBounds" : {
"son" : [
"[1.0, 1.0]"
],
"age" : [
"(1.0, 30.0)"
]
},


mongo3.4边界取的是对的,而且多了一个multiKeyPaths,指定了哪个是array字段

"multiKeyPaths" : {
"son" : [
"son"
],
"age" : []
},


因为mongo3.4中解决了,所以是mongo本身的一个bug,在jira中找到这条

https://jira.mongodb.org/browse/SERVER-15059

3.2及其以前的版本不会保存多键索引的哪个字段是array,所以对于像上面age这样的标量字段,mongo也不能确定table中是否有可能这个字段是否是array。所以会将age:{gt:1,lt:30}当成两个条件去查询,所以索引边界只能有一个。3.4版本进行了优化,增加了multiKeyPaths来存储哪个字段是多键索引字段。

扩展

这个优化方法治标不治本,因为mongo 虽然限定了一个联合索引中只有一个数组字段,但是因为mongo 是free schema.只要保证在一个文档中只有一个数组即可,而不必是同一字段。所以像上述问题,如果age字段也有值是array,那效果就和之前的一样的了。注意multiKeyPaths字段。

"inputStage" : {
"stage" : "IXSCAN",
"nReturned" : 3,
"executionTimeMillisEstimate" : 0,
"works" : 5,
"advanced" : 3,
"needTime" : 1,
"needYield" : 0,
"saveState" : 0,
"restoreState" : 0,
"isEOF" : 1,
"invalidates" : 0,
"keyPattern" : {
"son" : 1,
"age" : 1
},
"indexName" : "son_age",
"isMultiKey" : true,
"multiKeyPaths" : {
"son" : [
"son"
],
"age" : [
"age"
]
},
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 1,
"direction" : "forward",
"indexBounds" : {
"son" : [
"[1.0, 1.0]"
],
"age" : [
"[-inf.0, 30.0)"
]
},


总结

这个问题在mongo3.4中有修复,但是还拿出来讲,是觉得mongo数据库还是会有很多不完善的地方,在实际开发过程中不要想当然,查询还是要跑下查询计划,才能确保万无一失。

另外又是吐槽mongo的时候了,free schema有一定的好处,但是也不能太free了,像数组联合索引这个,不同字段都可以,只要保证同一个document只有一个数组,这个比较坑。实际开发中,还是做个ORM。为collection建个映射吧,要不乱插入一个不同类型的字段,就危险了

参考

https://docs.mongodb.com/manual/core/multikey-index-bounds/

https://jira.mongodb.org/browse/SERVER-15059
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: