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

摘要

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)"
                    ]
                },

总结

  1. 这个问题在mongo3.4中有修复,但是还拿出来讲,是觉得mongo数据库还是会有很多不完善的地方,在实际开发过程中不要想当然,查询还是要跑下查询计划,才能确保万无一失。
  2. 另外又是吐槽mongo的时候了,free schema有一定的好处,但是也不能太free了,像数组联合索引这个,不同字段都可以,只要保证同一个document只有一个数组,这个比较坑。实际开发中,还是做个ORM。为collection建个映射吧,要不乱插入一个不同类型的字段,就危险了

参考

https://docs.mongodb.com/manual/core/multikey-index-bounds/
https://jira.mongodb.org/browse/SERVER-15059

展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: Age of Ai 设计师: meimeiellie
应支付0元
点击重新获取
扫码支付

支付成功即可阅读