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