国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

MongoDB指南---10、索引、復合索引 簡介

leiyi / 1865人閱讀

摘要:可以通過來強制使用某個特定的索引,再次執行這個查詢,但是這次使用,作為索引。

上一篇文章:MongoDB指南---9、游標與數據庫命令
下一篇文章:MongoDB指南---11、使用復合索引、$操作符如何使用索引、索引對象和數組、索引基數

本章介紹MongoDB的索引,索引可以用來優化查詢,而且在某些特定類型的查詢中,索引是必不可少的。

什么是索引?為什么要用索引?

如何選擇需要建立索引的字段?

如何強制使用索引?如何評估索引的效率?

創建索引和刪除索引。

為集合選擇合適的索引是提高性能的關鍵。

1、 索引簡介

數據庫索引與書籍的索引類似。有了索引就不需要翻整本書,數據庫可以直接在索引中查找,在索引中找到條目以后,就可以直接跳轉到目標文檔的位置,這能使查找速度提高幾個數量級。
不使用索引的查詢稱為全表掃描(這個術語來自關系型數據庫),也就是說,服務器必須查找完一整本書才能找到查詢結果。這個處理過程與我們在一本沒有索引的書中查找信息很像:從第1頁開始一直讀完整本書。通常來說,應該盡量避免全表掃描,因為對于大集合來說,全表掃描的效率非常低。
來看一個例子,我們創建了一個擁有1 000 000個文檔的集合(如果你想要10 000 000或者100 000 000個文檔也行,只要你有那個耐心):

> for (i=0; i<1000000; i++) {
...     db.users.insert(
...         {
...             "i" : i,
...             "username" : "user"+i,
...             "age" : Math.floor(Math.random()*120),
...             "created" : new Date()
...         }
...     );
... }

如果在這個集合上做查詢,可以使用explain()函數查看MongoDB在執行查詢的過程中所做的事情。下面試著查詢一個隨機的用戶名:

> db.users.find({username: "user101"}).explain()
{
    "cursor" : "BasicCursor",
    "nscanned" : 1000000,
    "nscannedObjects" : 1000000,
    "n" : 1,
    "millis" : 721,
    "nYields" : 0,
    "nChunkSkips" : 0,
    "isMultiKey" : false,
    "indexOnly" : false,
    "indexBounds" : {

    }
}

5.2節會詳細介紹輸出信息里的這些字段,目前來說可以忽略大多數字段。"nscanned"是MongoDB在完成這個查詢的過程中掃描的文檔總數。可以看到,這個集合中的每個文檔都被掃描過了。也就是說,為了完成這個查詢,MongoDB查看了每一個文檔中的每一個字段。這個查詢耗費了將近1秒的時間才完成:"millis"字段顯示的是這個查詢耗費的毫秒數。
字段"n"顯示了查詢結果的數量,這里是1,因為這個集合中確實只有一個username為"user101"的文檔。注意,由于不知道集合里的username字段是唯一的,MongoDB不得不查看集合中的每一個文檔。為了優化查詢,將查詢結果限制為1,這樣MongoDB在找到一個文檔之后就會停止了:

> db.users.find({username: "user101"}).limit(1).explain()
{
    "cursor" : "BasicCursor",
    "nscanned" : 102,
    "nscannedObjects" : 102,
    "n" : 1,
    "millis" : 2,
    "nYields" : 0,
    "nChunkSkips" : 0,
    "isMultiKey" : false,
    "indexOnly" : false,
    "indexBounds" : {

    }
}

現在,所掃描的文檔數量極大地減少了,而且整個查詢幾乎是瞬間完成的。但是,這個方案是不現實的:如果要查找的是user999999呢?我們仍然不得不遍歷整個集合,而且,隨著用戶的增加,查詢會越來越慢。
對于此類查詢,索引是一個非常好的解決方案:索引可以根據給定的字段組織數據,讓MongoDB能夠非??斓卣业侥繕宋臋n。下面嘗試在username字段上創建一個索引:

> db.users.ensureIndex({"username" : 1})

由于機器性能和集合大小的不同,創建索引有可能需要花幾分鐘時間。如果對ensureIndex的調用沒能在幾秒鐘后返回,可以在另一個shell中執行db.currentOp()或者是檢查mongod的日志來查看索引創建的進度。
索引創建完成之后,再次執行最初的查詢:

> db.users.find({"username" : "user101"}).explain()
{
    "cursor" : "BtreeCursor username_1",
    "nscanned" : 1,
    "nscannedObjects" : 1,
    "n" : 1,
    "millis" : 3,
    "nYields" : 0,
    "nChunkSkips" : 0,
    "isMultiKey" : false,
    "indexOnly" : false,
    "indexBounds" : {
        "username" : [
            [
                "user101",
                "user101"
            ]
        ]
    }
}

這次explain()的輸出內容比之前復雜一些,但是目前我們只需要注意"n"、"nscanned"和"millis"這幾個字段,可以忽略其他字段??梢钥吹剑@個查詢現在幾乎是瞬間完成的(甚至可以更好),而且對于任意username的查詢,所耗費的時間基本一致:

> db.users.find({username: "user999999"}).explain().millis
1

可以看到,使用了索引的查詢幾乎可以瞬間完成,這是非常激動人心的。然而,使用索引是有代價的:對于添加的每一個索引,每次寫操作(插入、更新、刪除)都將耗費更多的時間。這是因為,當數據發生變動時,MongoDB不僅要更新文檔,還要更新集合上的所有索引。因此,MongoDB限制每個集合上最多只能有64個索引。通常,在一個特定的集合上,不應該擁有兩個以上的索引。于是,挑選合適的字段建立索引非常重要。

MongoDB的索引幾乎與傳統的關系型數據庫索引一模一樣,所以如果已經掌握了那些技巧,則可以跳過本節的語法說明。后面會介紹一些索引的基礎知識,但一定要記住這里涉及的只是冰山一角。絕大多數優化MySQL/Oracle/SQLite索引的技巧同樣也適用于MongoDB(包括“Use the Index, Luke”上的教程http://use-the-index-luke.com)。

為了選擇合適的鍵來建立索引,可以查看常用的查詢,以及那些需要被優化的查詢,從中找出一組常用的鍵。例如,在上面的例子中,查詢是在"username"上進行的。如果這是一個非常通用的查詢,或者這個查詢造成了性能瓶頸,那么在"username"上建立索引會是非常好的選擇。然而,如果這只是一個很少用到的查詢,或者只是給管理員用的查詢(管理員并不需要太在意查詢耗費的時間),那就不應該對"username"建立索引。

2、 復合索引簡介

索引的值是按一定順序排列的,因此,使用索引鍵對文檔進行排序非??臁H欢?,只有在首先使用索引鍵進行排序時,索引才有用。例如,在下面的排序里,"username"上的索引沒什么作用:

> db.users.find().sort({"age" : 1, "username" : 1})

這里先根據"age"排序再根據"username"排序,所以"username"在這里發揮的作用并不大。為了優化這個排序,可能需要在"age"和"username"上建立索引:

> db.users.ensureIndex({"age" : 1, "username" : 1})

這樣就建立了一個復合索引(compound index)。如果查詢中有多個排序方向或者查詢條件中有多個鍵,這個索引就會非常有用。復合索引就是一個建立在多個字段上的索引。
假如我們有一個users集合(如下所示),如果在這個集合上執行一個不排序(稱為自然順序)的查詢:

> db.users.find({}, {"_id" : 0, "i" : 0, "created" : 0})
{ "username" : "user0", "age" : 69 }
{ "username" : "user1", "age" : 50 }
{ "username" : "user2", "age" : 88 }
{ "username" : "user3", "age" : 52 }
{ "username" : "user4", "age" : 74 }
{ "username" : "user5", "age" : 104 }
{ "username" : "user6", "age" : 59 }
{ "username" : "user7", "age" : 102 }
{ "username" : "user8", "age" : 94 }
{ "username" : "user9", "age" : 7 }
{ "username" : "user10", "age" : 80 }
...

如果使用{"age" : 1, "username" : 1}建立索引,這個索引大致會是這個樣子:

[0, "user100309"] -> 0x0c965148
[0, "user100334"] -> 0xf51f818e
[0, "user100479"] -> 0x00fd7934
...
[0, "user99985" ] -> 0xd246648f
[1, "user100156"] -> 0xf78d5bdd
[1, "user100187"] -> 0x68ab28bd
[1, "user100192"] -> 0x5c7fb621
...
[1, "user999920"] -> 0x67ded4b7
[2, "user100141"] -> 0x3996dd46
[2, "user100149"] -> 0xfce68412
[2, "user100223"] -> 0x91106e23
...

每一個索引條目都包含一個"age"字段和一個"username"字段,并且指向文檔在磁盤上的存儲位置(這里使用十六進制數字表示,可以忽略)。注意,這里的"age"字段是嚴格升序排列的,"age"相同的條目按照"username"升序排列。每個"age"都有大約8000個對應的"username",這里只是挑選了少量數據用于傳達大概的信息。
MongoDB對這個索引的使用方式取決于查詢的類型。下面是三種主要的方式。

db.users.find({"age" : 21}).sort({"username" : -1})

這是一個點查詢(point query),用于查找單個值(盡管包含這個值的文檔可能有多個)。由于索引中的第二個字段,查詢結果已經是有序的了:MongoDB可以從{"age" : 21}匹配的最后一個索引開始,逆序依次遍歷索引:

[21, "user999977"] -> 0x9b3160cf
[21, "user999954"] -> 0xfe039231
[21, "user999902"] -> 0x719996aa
...

這種類型的查詢是非常高效的:MongoDB能夠直接定位到正確的年齡,而且不需要對結果進行排序(因為只需要對數據進行逆序遍歷就可以得到正確的順序了)。
注意,排序方向并不重要:MongoDB可以在任意方向上對索引進行遍歷。

db.users.find({"age" : {"$gte" : 21, "$lte" : 30}})

這是一個多值查詢(multi-value query),查找到多個值相匹配的文檔(在本例中,年齡必須介于21到30之間)。MongoDB會使用索引中的第一個鍵"age"得到匹配的文檔,如下所示:

[21, "user100000"] -> 0x37555a81
[21, "user100069"] -> 0x6951d16f
[21, "user1001"] ->   0x9a1f5e0c
[21, "user100253"] -> 0xd54bd959
[21, "user100409"] -> 0x824fef6c
[21, "user100469"] -> 0x5fba778b
...
[30, "user999775"] -> 0x45182d8c
[30, "user999850"] -> 0x1df279e9
[30, "user999936"] -> 0x525caa57

通常來說,如果MongoDB使用索引進行查詢,那么查詢結果文檔通常是按照索引順序排列的。

db.users.find({"age" : {"$gte" : 21, "$lte" : 30}}).sort({"username":1})

這是一個多值查詢,與上一個類似,只是這次需要對查詢結果進行排序。跟之前一樣,MongoDB會使用索引來匹配查詢條件:

[21, "user100000"] -> 0x37555a81
[21, "user100069"] -> 0x6951d16f
[21, "user1001"] ->   0x9a1f5e0c 
[21, "user100253"] -> 0xd54bd959
...
[22, "user100004"] -> 0x81e862c5
[22, "user100328"] -> 0x83376384
[22, "user100335"] -> 0x55932943
[22, "user100405"] -> 0x20e7e664
...

然而,使用這個索引得到的結果集中"username"是無序的,而查詢要求結果以"username"升序排列,所以MongoDB需要先在內存中對結果進行排序,然后才能返回。因此,這個查詢通常不如上一個高效。
當然,查詢速度取決于有多少個文檔與查詢條件匹配:如果結果集中只有少數幾個文檔,MongoDB對這些文檔進行排序并不需要耗費多少時間。如果結果集中的文檔數量比較多,查詢速度就會比較慢,甚至根本不能用:如果結果集的大小超過32 MB,MongoDB就會出錯,拒絕對如此多的數據進行排序:

Mon Oct 29 16:25:26 uncaught exception: error: {
    "$err" : "too much data for sort() with no index. add an index or
        specify a smaller limit",
    "code" : 10128
}

最后一個例子中,還可以使用另一個索引(同樣的鍵,但是順序調換了):{"username" : 1, "age" : 1}。MongoDB會反轉所有的索引條目,但是會以你期望的順序返回。MongoDB會根據索引中的"age"部分挑選出匹配的文檔:

["user0", 69]
["user1", 50]
["user10", 80]
["user100", 48]
["user1000", 111]
["user10000", 98]
["user100000", 21] -> 0x73f0b48d
["user100001", 60]
["user100002", 82]
["user100003", 27] -> 0x0078f55f
["user100004", 22] -> 0x5f0d3088
["user100005", 95]
...

這樣非常好,因為不需要在內存中對大量數據進行排序。但是,MongoDB不得不掃描整個索引以便找到所有匹配的文檔。因此,如果對查詢結果的范圍做了限制,那么MongoDB在幾次匹配之后就可以不再掃描索引,在這種情況下,將排序鍵放在第一位是一個非常好的策略。
可以通過explain()來查看MongoDB對db.users.find({"age" : {"$gte" : 21, "$lte" : 30}}).sort({"username" : 1})的默認行為:

 > db.users.find({"age" : {"$gte" : 21, "$lte" : 30}}).
... sort({"username" : 1}).
... explain()
{
    "cursor" : "BtreeCursor age_1_username_1",
    "isMultiKey" : false,
    "n" : 83484,
    "nscannedObjects" : 83484,
    "nscanned" : 83484,
    "nscannedObjectsAllPlans" : 83484,
    "nscannedAllPlans" : 83484,
    "scanAndOrder" : true,
    "indexOnly" : false,
    "nYields" : 0,
    "nChunkSkips" : 0,
    "millis" : 2766,
    "indexBounds" : {
        "age" : [
            [
                21,
                30
            ]
        ],
        "username" : [
            [
                {
                    "$minElement" : 1
                },
                {
                    "$maxElement" : 1
                }
            ]
        ]
    },
    "server" : "spock:27017"
}

可以忽略大部分字段,后面會有相關介紹。注意,"cursor"字段說明這次查詢使用的索引是 {"age" : 1, "user name" : 1},而且只查找了不到1/10的文檔("nscanned"只有83484),但是這個查詢耗費了差不多3秒的時間("millis"字段顯示的是毫秒數)。這里的"scanAndOrder"字段的值是true:說明MongoDB必須在內存中對數據進行排序,如之前所述。
可以通過hint來強制MongoDB使用某個特定的索引,再次執行這個查詢,但是這次使用{"username" : 1, "age" : 1}作為索引。這個查詢掃描的文檔比較多,但是不需要在內存中對數據排序:

> db.users.find({"age" : {"$gte" : 21, "$lte" : 30}}).
... sort({"username" : 1}).
... hint({"username" : 1, "age" : 1}).
... explain()
{
    "cursor" : "BtreeCursor username_1_age_1",
    "isMultiKey" : false,
    "n" : 83484,
    "nscannedObjects" : 83484,
    "nscanned" : 984434,
    "nscannedObjectsAllPlans" : 83484,
    "nscannedAllPlans" : 984434,
    "scanAndOrder" : false,
    "indexOnly" : false,
    "nYields" : 0,
    "nChunkSkips" : 0,
    "millis" : 14820,
    "indexBounds" : {
        "username" : [
            [
                {
                    "$minElement" : 1
                },
                {
                    "$maxElement" : 1
                }
            ]
        ],
        "age" : [
            [
                21,
                30
            ]
        ]
    },
    "server" : "spock:27017"
}

注意,這次查詢耗費了將近15秒才完成。對比鮮明,第一個索引速度更快。然而,如果限制每次查詢的結果數量,新的贏家產生了:

> db.users.find({"age" : {"$gte" : 21, "$lte" : 30}}).
... sort({"username" : 1}).
... limit(1000).
... hint({"age" : 1, "username" : 1}).
... explain()["millis"]
2031
> db.users.find({"age" : {"$gte" : 21, "$lte" : 30}}).
... sort({"username" : 1}).
... limit(1000).
... hint({"username" : 1, "age" : 1}).
... explain()["millis"]
181

第一個查詢耗費的時間仍然介于2秒到3秒之間,但是第二個查詢只用了不到1/5秒!因此,應該就在應用程序使用的查詢上執行explain()。排除掉那些可能會導致explain()輸出信息不準確的選項。
在實際的應用程序中,{"sortKey" : 1, "queryCriteria" : 1}索引通常是很有用的,因為大多數應用程序在一次查詢中只需要得到查詢結果最前面的少數結果,而不是所有可能的結果。而且,由于索引在內部的組織形式,這種方式非常易于擴展。索引本質上是樹,最小的值在最左邊的葉子上,最大的值在最右邊的葉子上。如果有一個日期類型的"sortKey"(或是其他能夠隨時間增加的值),當從左向右遍歷這棵樹時,你實際上也花費了時間。因此,如果應用程序需要使用最近數據的機會多于較老的數據,那么MongoDB只需在內存中保留這棵樹最右側的分支(最近的數據),而不必將整棵樹留在內存中。類似這樣的索引是右平衡的(right balanced),應該盡可能讓索引是右平衡的。"_id"索引就是一個典型的右平衡索引。

上一篇文章:MongoDB指南---9、游標與數據庫命令
下一篇文章:MongoDB指南---11、使用復合索引、$操作符如何使用索引、索引對象和數組、索引基數

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/44102.html

相關文章

  • MongoDB指南---10、索引、復合索引 簡介

    摘要:可以通過來強制使用某個特定的索引,再次執行這個查詢,但是這次使用,作為索引。 上一篇文章:MongoDB指南---9、游標與數據庫命令下一篇文章:MongoDB指南---11、使用復合索引、$操作符如何使用索引、索引對象和數組、索引基數 本章介紹MongoDB的索引,索引可以用來優化查詢,而且在某些特定類型的查詢中,索引是必不可少的。 什么是索引?為什么要用索引? 如何選擇需要建立...

    enrecul101 評論0 收藏0
  • MongoDB指南---11、使用復合索引、$操作符如何使用索引索引對象和數組、索引基數

    摘要:操作符如何使用索引有一些查詢完全無法使用索引,也有一些查詢能夠比其他查詢更高效地使用索引。有時能夠使用索引,但是通常它并不知道要如何使用索引。索引對象和數組允許深入文檔內部,對嵌套字段和數組建立索引。 上一篇文章:MongoDB指南---10、索引、復合索引 簡介下一篇文章:MongoDB指南---12、使用explain()和hint()、何時不應該使用索引 1、使用復合索引 在多...

    saucxs 評論0 收藏0
  • MongoDB指南---11、使用復合索引、$操作符如何使用索引索引對象和數組、索引基數

    摘要:操作符如何使用索引有一些查詢完全無法使用索引,也有一些查詢能夠比其他查詢更高效地使用索引。有時能夠使用索引,但是通常它并不知道要如何使用索引。索引對象和數組允許深入文檔內部,對嵌套字段和數組建立索引。 上一篇文章:MongoDB指南---10、索引、復合索引 簡介下一篇文章:MongoDB指南---12、使用explain()和hint()、何時不應該使用索引 1、使用復合索引 在多...

    tomlingtm 評論0 收藏0
  • MongoDB指南---1、MongoDB簡介

    摘要:不采用關系模型主要是為了獲得更好的擴展性。易于擴展應用程序數據集的大小正在以不可思議的速度增長。過去非常罕見的級別數據,現在已是司空見慣了。這種精簡方式的設計是能夠實現如此高性能的原因之一。下一篇文章指南基礎知識文檔集合數據庫客戶端 下一篇文章:MongoDB指南---2、MongoDB基礎知識-文檔、集合、數據庫、客戶端 MongoDB是一款強大、靈活,且易于擴展的通用型數據庫。它...

    VPointer 評論0 收藏0
  • MongoDB指南---1、MongoDB簡介

    摘要:不采用關系模型主要是為了獲得更好的擴展性。易于擴展應用程序數據集的大小正在以不可思議的速度增長。過去非常罕見的級別數據,現在已是司空見慣了。這種精簡方式的設計是能夠實現如此高性能的原因之一。下一篇文章指南基礎知識文檔集合數據庫客戶端 下一篇文章:MongoDB指南---2、MongoDB基礎知識-文檔、集合、數據庫、客戶端 MongoDB是一款強大、靈活,且易于擴展的通用型數據庫。它...

    guyan0319 評論0 收藏0

發表評論

0條評論

leiyi

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<