2013-03-21 11 views
1

我想對MongoDB集合中的字符串數組執行正則表達式查詢。我只能找到這個限制在docs爲什麼在MongoDB中對索引數組的正則表達式前綴查詢很慢?

$正則表達式只能有效地使用索引時正則表達式 有一個字符串的錨開始時(即^),是一個 情況敏感匹配

讓我們做一個試驗:

> for (var i=0; i<100000; i++) db.test.insert({f: ['a_0_'+i, 'a_1_2']}) 
> db.test.count() 
100000 
> db.test.ensureIndex({f: 1}) 
> db.test.find({f: /^a_(0)?_12$/ }) 
{ "_id" : ObjectId("514ac59886f004fe03ef2a96"), "f" : [ "a_0_12", "a_1_2" ] } 
> db.test.find({f: /^a_(0)?_12$/ }).explain() 
{ 
    "cursor" : "BtreeCursor f_1 multi", 
    "isMultiKey" : true, 
    "n" : 1, 
    "nscannedObjects" : 200000, 
    "nscanned" : 200000, 
    "nscannedObjectsAllPlans" : 200000, 
    "nscannedAllPlans" : 200000, 
    "scanAndOrder" : false, 
    "indexOnly" : false, 
    "nYields" : 0, 
    "nChunkSkips" : 0, 
    "millis" : 482, 
    "indexBounds" : { 
     "f" : [ 
      [ 
       "a_", 
       "a`" 
      ], 
      [ 
       /^a_(0)?_12$/, 
       /^a_(0)?_12$/ 
      ] 
     ] 
    }, 
    "server" : "someserver:27017" 
} 

查詢是sloooow。在另一方面,這種查詢是最優的:(但不適合我的使用情況)

> db.test.find({f: 'a_0_12' }).explain() 
{ 
    "cursor" : "BtreeCursor f_1", 
    "isMultiKey" : true, 
    "n" : 1, 
    "nscannedObjects" : 1, 
    "nscanned" : 1, 
    "nscannedObjectsAllPlans" : 1, 
    "nscannedAllPlans" : 1, 
    "scanAndOrder" : false, 
    "indexOnly" : false, 
    "nYields" : 0, 
    "nChunkSkips" : 0, 
    "millis" : 0, 
    "indexBounds" : { 
     "f" : [ 
      [ 
       "a_0_12", 
       "a_0_12" 
      ] 
     ] 
    }, 
    "server" : "someserver:27017" 
} 

爲什麼正則表達式查詢掃描所有(子)記錄時,它有一個指數?我錯過了什麼?

+0

它不能快。除了他們爲「開始」所做的簡單優化之外,搜索需要查看每個使用這些字符的文檔並在每個文檔上運行正則表達式。 – WiredPrairie 2013-03-21 10:43:13

+0

爲什麼會這樣?至少對於這個正則表達式,我沒有理由不能優化它。誠然,正則表達式狀態引擎需要與搜索結合,但我認爲這就是MongoDB正在做的事情。如果沒有,它是否僅僅在「(」和使用索引之前,一個一個地遍歷和檢查對方選項?)這只是......好吧,「不是最優的」。:)有人可以證實這一點? – johndodo 2013-03-21 14:20:24

+1

Mongodb索引包含字段'f'的全文字符串值。沒什麼比這更重要的了。它檢查字符串的起始位置,然後它需要在與根相匹配的每個文檔上運行RegEx。 'a_'是它可以用作根的唯一部分。每個與根相匹配的文檔必須針對正則表達式的其餘部分運行。您可以通過將第二部分作爲另一個字段存儲在文檔中而不是將其嵌入到字符串中來優化它。 (然後在兩個鍵上創建一個複合索引)。 – WiredPrairie 2013-03-21 19:15:37

回答

1

測試用例有幾個特點是沒有好處的正則表達式和索引使用:

  • 每個文檔包括兩個值的陣列都與「A_」開始。您的正則表達式/^a_(0)?_12$/正在查找一個以a開頭的字符串,後跟一個可選的「0」,因此會導致所有索引條目(200k值)的比較。
  • 您正則表達式也匹配文件有(a_1_2)的值,所以最終會不管相匹配的指數

的所有文件既然你有一個多鍵(數組索引),索引的數量實際上比對100k文檔的全表掃描更差。你可以用$natural暗示測試,看看:

db.test.find({f: /^a_(0|)12$/ }).hint({$natural:1}).explain() 
{ 
    "cursor" : "BasicCursor", 
    "isMultiKey" : false, 
    "n" : 0, 
    "nscannedObjects" : 100000, 
    "nscanned" : 100000, 
    "nscannedObjectsAllPlans" : 100000, 
    "nscannedAllPlans" : 100000, 
    "scanAndOrder" : false, 
    "indexOnly" : false, 
    "nYields" : 0, 
    "nChunkSkips" : 0, 
    "millis" : 192, 
    "indexBounds" : { 

    }, 
} 

更多的隨機數據或更具選擇性的正則表達式將導致更少的比較。

+0

感謝您的答案,的確,這是它是如何作品。 – johndodo 2013-03-22 11:29:01