2014-02-25 20 views
1

我對複合索引上的MongoDB查詢有一個(希望很快)的問題。繼續複合索引上的查詢(分頁)

說我有一個數據集(例如,評論),我想排序分數降序,然後日期:

{ "score" : 10, "date" : ISODate("2014-02-24T00:00:00.000Z"), ...} 
{ "score" : 10, "date" : ISODate("2014-02-18T00:00:00.000Z"), ...} 
{ "score" : 10, "date" : ISODate("2014-02-12T00:00:00.000Z"), ...} 
{ "score" : 9, "date" : ISODate("2014-02-22T00:00:00.000Z"), ...} 
{ "score" : 9, "date" : ISODate("2014-02-16T00:00:00.000Z"), ...} 
... 

我的理解迄今爲止的是,我可以做一個複合索引支持這個查詢,看起來像{"score":-1,"date":-1}。 (爲了清楚起見,我沒有在索引中使用日期,而是用於唯一的大致基於時間的訂單的ObjectID)

現在,假設我想支持通過評論進行分頁。第一頁很容易,我可以在光標的末尾貼一個.limit(n)選項。我正在努力的是繼續搜索。

我一直在提及MongoDB:權威指南由Kristina Chodorow。在本書中,克里斯蒂娜提到在大型數據集上使用skip()並不是很高效,並建議對最後一次看到的結果(例如上次看到的日期)的參數使用範圍查詢。

我想要做的是執行一個範圍查詢,作用於兩個字段,但將第二個字段視爲第一個字段的第二個字段(就像索引被排序一樣)。由於我的複合索引已經完全按照我想要的順序,似乎應該有一些方法可以通過指向索引中的特定元素並按排序順序遍歷它來跳入搜索。然而,從我對MongoDB中的查詢的理解(不可否認)看來,這似乎不可行。

據我所看到的,我有三種選擇:

  1. 使用skip()反正
  2. 無論是使用一個$或查詢或兩個不同查詢:{$or : [{"score" : lastScore, "date" : { $lt : lastDate}}, {'score' : {$lt : lastScore}]}
  3. 使用$max特殊查詢選項

3號似乎是最接近我的理想,但參考文字指出,'你應該通常使用「$ lt」而不是「$ ma X」'。

總之,我有幾個問題:

  1. 是否有某種方式來執行我的動作,我可能錯過了什麼? (跳入索引並按排序順序遍歷它)
  2. 如果不是,我描述的三個選項(或任何我忽略的)中的哪一個(在一般情況下)會在複合索引下提供最一致的性能?
  3. 爲什麼在大多數情況下$ lt優先於$ max?

在此先感謝您的幫助!

回答

2

另一種選擇是將scoredate存儲在子文檔中,然後對子文檔編制索引。例如:

{ 
    "a" : { "score" : 9, 
      "date" : ISODate("2014-02-22T00:00:00Z") }, 
    ... 
} 

db.foo.ensureIndex({ a : 1 }) 

db.foo.find({ a : { $lt : { score : lastScore, 
          date: lastDate } } }).sort({ a : -1 }) 

使用這種方法,你需要確保BSON子文檔中的域總是存儲在相同的順序,否則查詢將不會匹配你所期望的,因爲索引關鍵字進行比較是二進制整個BSON子文件的比較。

我會用$max指定上限,並配合$hint來確保數據庫使用您想要的索引。 $lt通常優於$max的原因是因爲$max使用指定的索引邊界來選擇索引。這意味着:

  • 選擇的指數可能不一定是最好的選擇。
  • 如果在具有不同排序順序的相同字段上存在多個索引,則索引的選擇可能不明確。

以上幾點詳細介紹了here

最後一點:max相當於$lte,不$lt,因此使用這種方法進行分頁,您需要跳過第一個返回的文件,以避免兩次輸出相同的文檔。

+0

感謝您的快速響應! $ max方法似乎對我來說最好,因爲查詢是Web應用程序執行的少數幾個之一,因此無論如何索引都將專門針對每個查詢進行精心構建(索引選擇無論如何都是固定的)。我很高興聽到$ max沒有重大問題。子文檔方法非常有趣,而且我也一定會考慮。我不確定它在整個數據模型的上下文中有多少意義,但是要感謝您完全回答這個問題! –