摘要:可以通過(guò)來(lái)強(qiáng)制使用某個(gè)特定的索引,再次執(zhí)行這個(gè)查詢,但是這次使用,作為索引。
上一篇文章:MongoDB指南---9、游標(biāo)與數(shù)據(jù)庫(kù)命令
下一篇文章:MongoDB指南---11、使用復(fù)合索引、$操作符如何使用索引、索引對(duì)象和數(shù)組、索引基數(shù)
本章介紹MongoDB的索引,索引可以用來(lái)優(yōu)化查詢,而且在某些特定類型的查詢中,索引是必不可少的。
什么是索引?為什么要用索引?
如何選擇需要建立索引的字段?
如何強(qiáng)制使用索引?如何評(píng)估索引的效率?
創(chuàng)建索引和刪除索引。
為集合選擇合適的索引是提高性能的關(guān)鍵。
1、 索引簡(jiǎn)介數(shù)據(jù)庫(kù)索引與書籍的索引類似。有了索引就不需要翻整本書,數(shù)據(jù)庫(kù)可以直接在索引中查找,在索引中找到條目以后,就可以直接跳轉(zhuǎn)到目標(biāo)文檔的位置,這能使查找速度提高幾個(gè)數(shù)量級(jí)。
不使用索引的查詢稱為全表掃描(這個(gè)術(shù)語(yǔ)來(lái)自關(guān)系型數(shù)據(jù)庫(kù)),也就是說(shuō),服務(wù)器必須查找完一整本書才能找到查詢結(jié)果。這個(gè)處理過(guò)程與我們?cè)谝槐緵]有索引的書中查找信息很像:從第1頁(yè)開始一直讀完整本書。通常來(lái)說(shuō),應(yīng)該盡量避免全表掃描,因?yàn)閷?duì)于大集合來(lái)說(shuō),全表掃描的效率非常低。
來(lái)看一個(gè)例子,我們創(chuàng)建了一個(gè)擁有1 000 000個(gè)文檔的集合(如果你想要10 000 000或者100 000 000個(gè)文檔也行,只要你有那個(gè)耐心):
> for (i=0; i<1000000; i++) { ... db.users.insert( ... { ... "i" : i, ... "username" : "user"+i, ... "age" : Math.floor(Math.random()*120), ... "created" : new Date() ... } ... ); ... }
如果在這個(gè)集合上做查詢,可以使用explain()函數(shù)查看MongoDB在執(zhí)行查詢的過(guò)程中所做的事情。下面試著查詢一個(gè)隨機(jī)的用戶名:
> 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節(jié)會(huì)詳細(xì)介紹輸出信息里的這些字段,目前來(lái)說(shuō)可以忽略大多數(shù)字段。"nscanned"是MongoDB在完成這個(gè)查詢的過(guò)程中掃描的文檔總數(shù)??梢钥吹?,這個(gè)集合中的每個(gè)文檔都被掃描過(guò)了。也就是說(shuō),為了完成這個(gè)查詢,MongoDB查看了每一個(gè)文檔中的每一個(gè)字段。這個(gè)查詢耗費(fèi)了將近1秒的時(shí)間才完成:"millis"字段顯示的是這個(gè)查詢耗費(fèi)的毫秒數(shù)。
字段"n"顯示了查詢結(jié)果的數(shù)量,這里是1,因?yàn)檫@個(gè)集合中確實(shí)只有一個(gè)username為"user101"的文檔。注意,由于不知道集合里的username字段是唯一的,MongoDB不得不查看集合中的每一個(gè)文檔。為了優(yōu)化查詢,將查詢結(jié)果限制為1,這樣MongoDB在找到一個(gè)文檔之后就會(huì)停止了:
> 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" : { } }
現(xiàn)在,所掃描的文檔數(shù)量極大地減少了,而且整個(gè)查詢幾乎是瞬間完成的。但是,這個(gè)方案是不現(xiàn)實(shí)的:如果要查找的是user999999呢?我們?nèi)匀徊坏貌槐闅v整個(gè)集合,而且,隨著用戶的增加,查詢會(huì)越來(lái)越慢。
對(duì)于此類查詢,索引是一個(gè)非常好的解決方案:索引可以根據(jù)給定的字段組織數(shù)據(jù),讓MongoDB能夠非常快地找到目標(biāo)文檔。下面嘗試在username字段上創(chuàng)建一個(gè)索引:
> db.users.ensureIndex({"username" : 1})
由于機(jī)器性能和集合大小的不同,創(chuàng)建索引有可能需要花幾分鐘時(shí)間。如果對(duì)ensureIndex的調(diào)用沒能在幾秒鐘后返回,可以在另一個(gè)shell中執(zhí)行db.currentOp()或者是檢查mongod的日志來(lái)查看索引創(chuàng)建的進(jìn)度。
索引創(chuàng)建完成之后,再次執(zhí)行最初的查詢:
> 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èi)容比之前復(fù)雜一些,但是目前我們只需要注意"n"、"nscanned"和"millis"這幾個(gè)字段,可以忽略其他字段。可以看到,這個(gè)查詢現(xiàn)在幾乎是瞬間完成的(甚至可以更好),而且對(duì)于任意username的查詢,所耗費(fèi)的時(shí)間基本一致:
> db.users.find({username: "user999999"}).explain().millis 1
可以看到,使用了索引的查詢幾乎可以瞬間完成,這是非常激動(dòng)人心的。然而,使用索引是有代價(jià)的:對(duì)于添加的每一個(gè)索引,每次寫操作(插入、更新、刪除)都將耗費(fèi)更多的時(shí)間。這是因?yàn)?,?dāng)數(shù)據(jù)發(fā)生變動(dòng)時(shí),MongoDB不僅要更新文檔,還要更新集合上的所有索引。因此,MongoDB限制每個(gè)集合上最多只能有64個(gè)索引。通常,在一個(gè)特定的集合上,不應(yīng)該擁有兩個(gè)以上的索引。于是,挑選合適的字段建立索引非常重要。
MongoDB的索引幾乎與傳統(tǒng)的關(guān)系型數(shù)據(jù)庫(kù)索引一模一樣,所以如果已經(jīng)掌握了那些技巧,則可以跳過(guò)本節(jié)的語(yǔ)法說(shuō)明。后面會(huì)介紹一些索引的基礎(chǔ)知識(shí),但一定要記住這里涉及的只是冰山一角。絕大多數(shù)優(yōu)化MySQL/Oracle/SQLite索引的技巧同樣也適用于MongoDB(包括“Use the Index, Luke”上的教程http://use-the-index-luke.com)。
為了選擇合適的鍵來(lái)建立索引,可以查看常用的查詢,以及那些需要被優(yōu)化的查詢,從中找出一組常用的鍵。例如,在上面的例子中,查詢是在"username"上進(jìn)行的。如果這是一個(gè)非常通用的查詢,或者這個(gè)查詢?cè)斐闪诵阅芷款i,那么在"username"上建立索引會(huì)是非常好的選擇。然而,如果這只是一個(gè)很少用到的查詢,或者只是給管理員用的查詢(管理員并不需要太在意查詢耗費(fèi)的時(shí)間),那就不應(yīng)該對(duì)"username"建立索引。
2、 復(fù)合索引簡(jiǎn)介索引的值是按一定順序排列的,因此,使用索引鍵對(duì)文檔進(jìn)行排序非常快。然而,只有在首先使用索引鍵進(jìn)行排序時(shí),索引才有用。例如,在下面的排序里,"username"上的索引沒什么作用:
> db.users.find().sort({"age" : 1, "username" : 1})
這里先根據(jù)"age"排序再根據(jù)"username"排序,所以"username"在這里發(fā)揮的作用并不大。為了優(yōu)化這個(gè)排序,可能需要在"age"和"username"上建立索引:
> db.users.ensureIndex({"age" : 1, "username" : 1})
這樣就建立了一個(gè)復(fù)合索引(compound index)。如果查詢中有多個(gè)排序方向或者查詢條件中有多個(gè)鍵,這個(gè)索引就會(huì)非常有用。復(fù)合索引就是一個(gè)建立在多個(gè)字段上的索引。
假如我們有一個(gè)users集合(如下所示),如果在這個(gè)集合上執(zhí)行一個(gè)不排序(稱為自然順序)的查詢:
> 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}建立索引,這個(gè)索引大致會(huì)是這個(gè)樣子:
[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 ...
每一個(gè)索引條目都包含一個(gè)"age"字段和一個(gè)"username"字段,并且指向文檔在磁盤上的存儲(chǔ)位置(這里使用十六進(jìn)制數(shù)字表示,可以忽略)。注意,這里的"age"字段是嚴(yán)格升序排列的,"age"相同的條目按照"username"升序排列。每個(gè)"age"都有大約8000個(gè)對(duì)應(yīng)的"username",這里只是挑選了少量數(shù)據(jù)用于傳達(dá)大概的信息。
MongoDB對(duì)這個(gè)索引的使用方式取決于查詢的類型。下面是三種主要的方式。
db.users.find({"age" : 21}).sort({"username" : -1})
這是一個(gè)點(diǎn)查詢(point query),用于查找單個(gè)值(盡管包含這個(gè)值的文檔可能有多個(gè))。由于索引中的第二個(gè)字段,查詢結(jié)果已經(jīng)是有序的了:MongoDB可以從{"age" : 21}匹配的最后一個(gè)索引開始,逆序依次遍歷索引:
[21, "user999977"] -> 0x9b3160cf [21, "user999954"] -> 0xfe039231 [21, "user999902"] -> 0x719996aa ...
這種類型的查詢是非常高效的:MongoDB能夠直接定位到正確的年齡,而且不需要對(duì)結(jié)果進(jìn)行排序(因?yàn)橹恍枰獙?duì)數(shù)據(jù)進(jìn)行逆序遍歷就可以得到正確的順序了)。
注意,排序方向并不重要:MongoDB可以在任意方向上對(duì)索引進(jìn)行遍歷。
db.users.find({"age" : {"$gte" : 21, "$lte" : 30}})
這是一個(gè)多值查詢(multi-value query),查找到多個(gè)值相匹配的文檔(在本例中,年齡必須介于21到30之間)。MongoDB會(huì)使用索引中的第一個(gè)鍵"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
通常來(lái)說(shuō),如果MongoDB使用索引進(jìn)行查詢,那么查詢結(jié)果文檔通常是按照索引順序排列的。
db.users.find({"age" : {"$gte" : 21, "$lte" : 30}}).sort({"username":1})
這是一個(gè)多值查詢,與上一個(gè)類似,只是這次需要對(duì)查詢結(jié)果進(jìn)行排序。跟之前一樣,MongoDB會(huì)使用索引來(lái)匹配查詢條件:
[21, "user100000"] -> 0x37555a81 [21, "user100069"] -> 0x6951d16f [21, "user1001"] -> 0x9a1f5e0c [21, "user100253"] -> 0xd54bd959 ... [22, "user100004"] -> 0x81e862c5 [22, "user100328"] -> 0x83376384 [22, "user100335"] -> 0x55932943 [22, "user100405"] -> 0x20e7e664 ...
然而,使用這個(gè)索引得到的結(jié)果集中"username"是無(wú)序的,而查詢要求結(jié)果以"username"升序排列,所以MongoDB需要先在內(nèi)存中對(duì)結(jié)果進(jìn)行排序,然后才能返回。因此,這個(gè)查詢通常不如上一個(gè)高效。
當(dāng)然,查詢速度取決于有多少個(gè)文檔與查詢條件匹配:如果結(jié)果集中只有少數(shù)幾個(gè)文檔,MongoDB對(duì)這些文檔進(jìn)行排序并不需要耗費(fèi)多少時(shí)間。如果結(jié)果集中的文檔數(shù)量比較多,查詢速度就會(huì)比較慢,甚至根本不能用:如果結(jié)果集的大小超過(guò)32 MB,MongoDB就會(huì)出錯(cuò),拒絕對(duì)如此多的數(shù)據(jù)進(jìn)行排序:
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 }
最后一個(gè)例子中,還可以使用另一個(gè)索引(同樣的鍵,但是順序調(diào)換了):{"username" : 1, "age" : 1}。MongoDB會(huì)反轉(zhuǎn)所有的索引條目,但是會(huì)以你期望的順序返回。MongoDB會(huì)根據(jù)索引中的"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] ...
這樣非常好,因?yàn)椴恍枰趦?nèi)存中對(duì)大量數(shù)據(jù)進(jìn)行排序。但是,MongoDB不得不掃描整個(gè)索引以便找到所有匹配的文檔。因此,如果對(duì)查詢結(jié)果的范圍做了限制,那么MongoDB在幾次匹配之后就可以不再掃描索引,在這種情況下,將排序鍵放在第一位是一個(gè)非常好的策略。
可以通過(guò)explain()來(lái)查看MongoDB對(duì)db.users.find({"age" : {"$gte" : 21, "$lte" : 30}}).sort({"username" : 1})的默認(rèn)行為:
> 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" }
可以忽略大部分字段,后面會(huì)有相關(guān)介紹。注意,"cursor"字段說(shuō)明這次查詢使用的索引是 {"age" : 1, "user name" : 1},而且只查找了不到1/10的文檔("nscanned"只有83484),但是這個(gè)查詢耗費(fèi)了差不多3秒的時(shí)間("millis"字段顯示的是毫秒數(shù))。這里的"scanAndOrder"字段的值是true:說(shuō)明MongoDB必須在內(nèi)存中對(duì)數(shù)據(jù)進(jìn)行排序,如之前所述。
可以通過(guò)hint來(lái)強(qiáng)制MongoDB使用某個(gè)特定的索引,再次執(zhí)行這個(gè)查詢,但是這次使用{"username" : 1, "age" : 1}作為索引。這個(gè)查詢掃描的文檔比較多,但是不需要在內(nèi)存中對(duì)數(shù)據(jù)排序:
> 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" }
注意,這次查詢耗費(fèi)了將近15秒才完成。對(duì)比鮮明,第一個(gè)索引速度更快。然而,如果限制每次查詢的結(jié)果數(shù)量,新的贏家產(chǎn)生了:
> 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
第一個(gè)查詢耗費(fèi)的時(shí)間仍然介于2秒到3秒之間,但是第二個(gè)查詢只用了不到1/5秒!因此,應(yīng)該就在應(yīng)用程序使用的查詢上執(zhí)行explain()。排除掉那些可能會(huì)導(dǎo)致explain()輸出信息不準(zhǔn)確的選項(xiàng)。
在實(shí)際的應(yīng)用程序中,{"sortKey" : 1, "queryCriteria" : 1}索引通常是很有用的,因?yàn)榇蠖鄶?shù)應(yīng)用程序在一次查詢中只需要得到查詢結(jié)果最前面的少數(shù)結(jié)果,而不是所有可能的結(jié)果。而且,由于索引在內(nèi)部的組織形式,這種方式非常易于擴(kuò)展。索引本質(zhì)上是樹,最小的值在最左邊的葉子上,最大的值在最右邊的葉子上。如果有一個(gè)日期類型的"sortKey"(或是其他能夠隨時(shí)間增加的值),當(dāng)從左向右遍歷這棵樹時(shí),你實(shí)際上也花費(fèi)了時(shí)間。因此,如果應(yīng)用程序需要使用最近數(shù)據(jù)的機(jī)會(huì)多于較老的數(shù)據(jù),那么MongoDB只需在內(nèi)存中保留這棵樹最右側(cè)的分支(最近的數(shù)據(jù)),而不必將整棵樹留在內(nèi)存中。類似這樣的索引是右平衡的(right balanced),應(yīng)該盡可能讓索引是右平衡的。"_id"索引就是一個(gè)典型的右平衡索引。
上一篇文章:MongoDB指南---9、游標(biāo)與數(shù)據(jù)庫(kù)命令
下一篇文章:MongoDB指南---11、使用復(fù)合索引、$操作符如何使用索引、索引對(duì)象和數(shù)組、索引基數(shù)
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/19582.html
摘要:可以通過(guò)來(lái)強(qiáng)制使用某個(gè)特定的索引,再次執(zhí)行這個(gè)查詢,但是這次使用,作為索引。 上一篇文章:MongoDB指南---9、游標(biāo)與數(shù)據(jù)庫(kù)命令下一篇文章:MongoDB指南---11、使用復(fù)合索引、$操作符如何使用索引、索引對(duì)象和數(shù)組、索引基數(shù) 本章介紹MongoDB的索引,索引可以用來(lái)優(yōu)化查詢,而且在某些特定類型的查詢中,索引是必不可少的。 什么是索引?為什么要用索引? 如何選擇需要建立...
摘要:操作符如何使用索引有一些查詢完全無(wú)法使用索引,也有一些查詢能夠比其他查詢更高效地使用索引。有時(shí)能夠使用索引,但是通常它并不知道要如何使用索引。索引對(duì)象和數(shù)組允許深入文檔內(nèi)部,對(duì)嵌套字段和數(shù)組建立索引。 上一篇文章:MongoDB指南---10、索引、復(fù)合索引 簡(jiǎn)介下一篇文章:MongoDB指南---12、使用explain()和hint()、何時(shí)不應(yīng)該使用索引 1、使用復(fù)合索引 在多...
摘要:操作符如何使用索引有一些查詢完全無(wú)法使用索引,也有一些查詢能夠比其他查詢更高效地使用索引。有時(shí)能夠使用索引,但是通常它并不知道要如何使用索引。索引對(duì)象和數(shù)組允許深入文檔內(nèi)部,對(duì)嵌套字段和數(shù)組建立索引。 上一篇文章:MongoDB指南---10、索引、復(fù)合索引 簡(jiǎn)介下一篇文章:MongoDB指南---12、使用explain()和hint()、何時(shí)不應(yīng)該使用索引 1、使用復(fù)合索引 在多...
摘要:不采用關(guān)系模型主要是為了獲得更好的擴(kuò)展性。易于擴(kuò)展應(yīng)用程序數(shù)據(jù)集的大小正在以不可思議的速度增長(zhǎng)。過(guò)去非常罕見的級(jí)別數(shù)據(jù),現(xiàn)在已是司空見慣了。這種精簡(jiǎn)方式的設(shè)計(jì)是能夠?qū)崿F(xiàn)如此高性能的原因之一。下一篇文章指南基礎(chǔ)知識(shí)文檔集合數(shù)據(jù)庫(kù)客戶端 下一篇文章:MongoDB指南---2、MongoDB基礎(chǔ)知識(shí)-文檔、集合、數(shù)據(jù)庫(kù)、客戶端 MongoDB是一款強(qiáng)大、靈活,且易于擴(kuò)展的通用型數(shù)據(jù)庫(kù)。它...
摘要:不采用關(guān)系模型主要是為了獲得更好的擴(kuò)展性。易于擴(kuò)展應(yīng)用程序數(shù)據(jù)集的大小正在以不可思議的速度增長(zhǎng)。過(guò)去非常罕見的級(jí)別數(shù)據(jù),現(xiàn)在已是司空見慣了。這種精簡(jiǎn)方式的設(shè)計(jì)是能夠?qū)崿F(xiàn)如此高性能的原因之一。下一篇文章指南基礎(chǔ)知識(shí)文檔集合數(shù)據(jù)庫(kù)客戶端 下一篇文章:MongoDB指南---2、MongoDB基礎(chǔ)知識(shí)-文檔、集合、數(shù)據(jù)庫(kù)、客戶端 MongoDB是一款強(qiáng)大、靈活,且易于擴(kuò)展的通用型數(shù)據(jù)庫(kù)。它...
閱讀 1591·2021-11-23 10:01
閱讀 2978·2021-11-19 09:40
閱讀 3228·2021-10-18 13:24
閱讀 3482·2019-08-29 14:20
閱讀 2989·2019-08-26 13:39
閱讀 1282·2019-08-26 11:56
閱讀 2678·2019-08-23 18:03
閱讀 384·2019-08-23 15:35