Mongodb内嵌文档索引创建问题


背景

需要创建数据全字段索引,因此把带检索数据列以内嵌文档的方式写入,在内嵌文档上加索引,加速查询

方案1:内嵌文档上加索引

数据存储格式如下:

{ 
    "_id" : "AB16105869340072961", 
    "info" : {
        "payment_bank_code" : "BRI", 
        "order_status" : "COMPLETED"
    }
}
{ 
    "_id" : "AB16105869340072962", 
    "info" : {
        "payment_bank_code" : "BRI", 
        "order_status" : "COMPLETED"
    }
}

在内嵌文档info上创建索引,并测试查询是否走索引,如下:

## 创建索引
db.demo1.createIndex( { "info": 1 } )

## 1、索引不生效,可以查出数据
db.demo1.find({"info.payment_bank_code" : "BRI"})
## 2、索引不生效,可以查出数据
db.demo1.find({"info.order_status" : "COMPLETED"})
## 3、索引不生效,可以查出数据
select * from demo1 where info.payment_bank_code='BRI'
## 4、索引不生效,可以查出数据
select * from demo1 where info.order_status='COMPLETED'
## 5、索引不生效,可以查出数据
select * from demo1 where info.payment_bank_code='BRI' and info.order_status='COMPLETED'

## 6、索引生效,但是查不出数据
db.demo1.find({info: {payment_bank_code:"BRI"}})
## 7、索引生效,但是查不出数据
db.demo1.find({info: {order_status:"COMPLETED"}})
## 8、索引生效,但是查不出数据
db.demo1.find({info: {order_status:"COMPLETED",payment_bank_code:"BRI"}})
## 9、索引生效,可以查出数据
db.demo1.find({info: {payment_bank_code:"BRI",order_status:"COMPLETED"}})

从上述测试可以看出,内嵌索引必须精确匹配(所有字段都必须匹配),且字段顺序很重要。同时,也需要注意1、3是等效的,但是3、6并不等效,最开始以为等效,看执行计划时一直不走索引。那么,像6这种如何转换为sql语法呢?目前没有找到

所以,方案1不能满足全字段索引

方案2:基于键值对的复合索引

数据存储格式如下:

{ 
    "_id" : "AB16105869340072961", 
    "info" : [
        {"k": "payment_bank_code" ,"v": "BRI"}, 
        {"k": "order_status" ,"v": "COMPLETED"}
    ]
}
{ 
    "_id" : "AB16105869340072962", 
     "info" : [
        {"k": "payment_bank_code" ,"v": "BRI"}, 
        {"k": "order_status" ,"v": "COMPLETED"}
    ]
}

在内嵌文档info上创建基于键值对的复合索引,并测试查询是否走索引,如下:

## 创建索引
db.demo1.createIndex( {"info.k": 1, "info.v": 1})

## 1、索引不生效,可以查出数据,不满足最左匹配原则
select * from demo1 where info.v='BRI'
## 2、索引不生效,可以查出数据,不满足最左匹配原则,等效1
db.demo1.find({"info.v":'BRI'})
## 3、索引不生效,查不出数据,不满足内嵌文档精确匹配
db.demo1.find({info:{k:'payment_bank_code'}})
## 4、索引不生效,查不出数据,不满足内嵌文档字段有序
db.demo1.find({info:{v:'BRI',k:'payment_bank_code'}})
## 5、索引不生效,可以查出数据,内嵌文档必须精确匹配,且字段有序
db.demo1.find({info:{k:'payment_bank_code',v:'BRI'}})

## 6、索引生效,可以查出数据,满足最左匹配原则
select * from demo1 where info.k='payment_bank_code'
## 7、索引生效,可以查出数据
select * from demo1 where info.k='payment_bank_code' and info.v='BRI'
## 8、索引生效,自动优化顺序,可以查出数据,等效7
select * from demo1 where info.v='BRI' and info.k='payment_bank_code' 
## 9、索引生效,可以查出数据,等效7、8
select * from demo1 where info.k='order_status' and info.v='COMPLETED'
## 10、索引生效,可以查出数据,or会并行扫描多次索引后union+deduplicate数据
select * from demo1 where (info.k='order_status' and info.v='COMPLETED')
or (info.k='payment_bank_code' and info.v='BRI')
## 11、索引生效,可以查出数据,等效6
db.demo1.find({"info.k":'payment_bank_code'})
## 12、索引生效,可以查出数据,等效7
db.demo1.find({"info.k":'payment_bank_code',"info.v":'BRI'})
## 13、索引生效,可以查出数据,等效8
db.demo1.find({"info.v":'BRI',"info.k":'payment_bank_code'})
## 14、索引生效,可以查出数据,使用$elemMatch,可以复合“评级”的界限
db.demo1.find({"info": { $elemMatch: {k: "payment_bank_code", v: "BRI"} }})

从上述测试可以看出,方案2满足全字段索引的需要,且支持sql语句的写法。缺点也很明显,需要同时指定k、v,写起来比较麻烦。

同时需要注意以下几点:

  • 结合方案1中测试,可以看出索引加在内嵌文档和具体字段上如何使索引生效的写法是不同
  • $elemMatch可以复合“评级”的界限,从执行计划上indexBounds信息可以看出,加上后info.v从[MinKey, MaxKey]变为["BRI", "BRI"],缩小检索范围,查询耗时减小。

为什么不加$elemMatch检索范围变大?

$elemMatch的官方定义如下:

The $elemMatch operator matches documents that contain an array field with at least one element that matches all the specified query criteria.
要求数组中至少一个元素匹配所有指定的查询条件,而不使用$elemMatch,则不要求匹配的是同一个元素,不同的条件可能匹配的是数组中不同的元素,示例如下:

## 该查询同样走索引,且可以匹配出数据,但实际info.v匹配的是order_status对应的值
select * from demo1 where info.k='payment_bank_code' and info.v='COMPLETED'

由上可见只要数组上中元素可以匹配上即可,不要求必须是其中某一项元素完全匹配。因此,可以设想一下索引的扫描方式。由于创建的是联合索引,info.k直接扫描索引即可,如果使用$elemMatch,info.v需要匹配同一元素,也走联合索引,根据info.v的值可以进一步缩小联合索引扫描范围。反之,只能扫描符合info.k前缀的索引(较前者扫描范围大),然后回表获取数据,在已匹配数据上进行info.v过滤。通过对比可以看出,后者扫描联合索引范围较大,且存在大量回表,最终还需要进行二次过滤,不仅可能不是我们想要的结果,而且可能非常耗时。

方案3:多键索引

数据存储格式如下:

{ 
    "_id" : "AB16105869340072961", 
    "info" : [
        {"payment_bank_code" : "BRI"}, 
        { "order_status" : "COMPLETED"}
    ]
}
{ 
    "_id" : "AB16105869340072962", 
     "info" : [
        {"payment_bank_code" : "BRI"}, 
        { "order_status" : "COMPLETED"}
    ]
}

在数组info上创建多键索引,并测试查询是否走索引,如下:

## 创建索引
db.demo1.createIndex( {"info": 1})

## 1、索引不生效,可以查出数据,写法不对,索引不在内嵌文档字段上
select * from demo1 where info.order_status='COMPLETED'
## 2、索引不生效,可以查出数据,同1
select * from demo1 where info.payment_bank_code='BRI'

## 3、索引生效,可以查出数据
db.demo1.find({info:{'payment_bank_code':'BRI'}})
## 4、索引生效,可以查出数据
db.demo1.find({info:{'order_status':'COMPLETED'}})
## 5、索引生效,查不出数据,走的时内嵌文档的精确匹配
db.demo1.find({info:{'payment_bank_code':'BRI','order_status':'COMPLETED'}})
## 6、索引生效,多条件交集需要使用$and精确匹配内嵌文档后交集
db.demo1.find({"$and" : [{info:{'payment_bank_code':'BRI'}},{info:{'order_status':'COMPLETED'}}]})
## 7、索引生效,多条件合集需要使用$or精确匹配内嵌文档和求和
db.demo1.find({"$or" : [{info:{'payment_bank_code':'BRI'}},{info:{'order_status':'COMPLETED'}}]})
## 8、索引生效,排除指定条件的内嵌文档需要使用$ne
db.demo1.find({info:{$ne:{'payment_bank_code':'BRI'}}})

从上述测试可以看出,方案3满足全字段索引的需要,数据结构更简单清晰,查询耗时较方案2有提升。缺点也很明显,不支持sql语法,复杂的查询条件脚本还是很复杂的,且生成的索引文件较方案2大不少。

参考:


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM