Mongodb内嵌文档索引创建问题

背景

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

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

数据存储格式如下:

{ 
    "_id" : "AB16105869340072961", 
    "info" : {
        "payment_bank_code" : "XXX", 
        "order_status" : "COMPLETED"
    }
}
{ 
    "_id" : "AB16105869340072962", 
    "info" : {
        "payment_bank_code" : "XXX", 
        "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": "XXX"}, 
        {"k": "order_status" ,"v": "COMPLETED"}
    ]
}
{ 
    "_id" : "AB16105869340072962", 
     "info" : [
        {"k": "payment_bank_code" ,"v": "XXX"}, 
        {"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"],缩小检索范围,查询耗时减小

方案3:多键索引

数据存储格式如下:

{ 
    "_id" : "AB16105869340072961", 
    "info" : [
        {"payment_bank_code" : "XXX"}, 
        { "order_status" : "COMPLETED"}
    ]
}
{ 
    "_id" : "AB16105869340072962", 
     "info" : [
        {"payment_bank_code" : "XXX"}, 
        { "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‘:‘COMPLETED1‘}}]})
## 7、索引生效,多条件合集需要使用$or精确匹配内嵌文档和求和
db.demo1.find({"$or" : [{info:{‘payment_bank_code‘:‘BRI‘}},{info:{‘order_status‘:‘COMPLETED1‘}}]})
## 8、索引生效,排除指定条件的内嵌文档需要使用$ne
db.demo1.find({info:{$ne:{‘payment_bank_code‘:‘BRI1‘}}})

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

参考:

Mongodb内嵌文档索引创建问题

上一篇:java.sql.SQLException: null, message from server: "Host 'DESKTOP-1PCF1N3' is not allowed to connect to this MySQL server"......


下一篇:如何阅读MySQL源码