標籤:
插入並儲存文檔
對目的地組使用insert方法插入一個文檔:
> db.foo.insert({"bar" : "baz"})
這個操作會給文檔增加一個"_id"鍵(要是原來沒有的話),然後將其儲存到MongoDB中。
批量插入
如果要插入多個文檔,使用批量插入會快一些。批量插入傳遞一個由文檔構成的數組給資料庫。
如果只是匯入資料(例如,從資料feed或者MySQL中匯入),可以使用命令列工具,如mongoimport,而不是使用批量插入。
刪除文檔
> db.users.remove()
上述操作會伸出users集合中所有的文檔。但不會刪除集合本身,原有的索引也會保留。
remove函數可以接受一個查詢文檔作為選擇性參數。給定這個參數以後,只有合格文檔才被刪除。列入,假設要刪除mailing.list集合中所有"out-put"為true的人:
> db.mailing.list.remove({"opt-out" : true})
刪除資料是永久性地,不能撤銷,也不能恢複。
刪除速度
刪除文檔通常會很快,但是要清楚整個集合,直接刪除(然後重建索引)hi更快。
例如,在Python中,使用如下方法插入一百萬個虛擬元素:
for i in range(1000000):
collection.insert({"foo": "bar", "baz": i, "z": 10 -i })
現在把剛插入的文檔都刪除,並記錄花費的時間。首先來看一個簡單的刪除(remove):
import timefrom pymongo import Collectiondb = Collection().foocollection = db.barstart = time.time()collection.remove()collection.find_one()total = time.time() - startprint "%d seconds" % total
這段指令碼輸出"46.08 seconds"
如果用db.drop_collection("bar")來代替remove和find_one,只花了.01秒,速度提升相當明顯。
更新文檔
Ⅰ使用update方法修改存入資料庫以後的文檔。update有兩個參數,一個是查詢文檔,用來找出需要的文檔,另一個是修改器(modifier)文檔,描述對修改的文檔做哪些修改。
例:
> joe = db.people.findOne({"name" : "joe", "age" : 20})
> joe.age++;
> db.people.update({"name" : "joe"}, joe)
Ⅱ使用修改器
通常文檔只會有一部分要更新。利用原子的更新修改器,可以使得這種部分更新極為搞笑。更新修改器是種特殊的鍵,用來指定複雜的更新操作,比如調整、增加或者刪除鍵,還可能是運算元組或者內嵌文檔。
假設要在一個集合中防止網站的分析資料,每當有人訪問頁面的時候,就要增加計數器。可以使用更新修改器原子性地完成這個增加。每個URL對應的訪問次數都以如下的方式儲存在文檔中:
{
"_id" : ObjectId("4b253b067525f35f94b60a31"),
"url" : "www.example.com",
"pageviews" : 52
}
每次有人訪問,就通過URL找到該頁面,並用"$inc"修改器增加"pageviews"的值。
> db.analytics.update({"url" : "www.example.com"}, {"$inc" : {"pageviews" : 1}})
接著,執行一個find操作,會發現"pageviews"的值增加了1.
> db.analytics.find()
{
"_id" : ObjectId("4b253b067525f35f94b60a31"),
"url" : "www.example.com",
"pageviews" : 53
}
①"$set"修改器
"$set"用來指定一個鍵的值。如果這個鍵不存在,則建立它。
例:添加喜歡的書到使用者資訊:
> db.users.update({"_id" : ObjectId("4b253b067525f35f94b60a31")}, {"$set" : {"favorite book" : "war and peace"}})
如果使用者覺得喜歡的另一本書:
> db.users.update({"name" : "joe"}, {"$set" : {"favorite book" : "green eggs and ham"}})
用"$set"可以修改鍵的資料類型。例如,如果使用者喜歡的是一堆書:
> db.users.update({"name" : "joe"} : {"$set" : {"favorite book" : ["cat‘s cradle", "fundation trilogy", "ender‘s game"]}})
如果使用者發現自己不愛讀書,可以用"$unset"將鍵完全刪除:
> db.users.update({"name" : "joe"}, {"$unset" : {"favorite book" : 1}})
②增加和減少
> db.game.update({"game" :"pinball", "user" : "joe"}, {"$inc" : {"score" : 50}})
③數組修改器
如果指定的鍵不存在,"$push"會向已有的數組末尾加入一個元素,要是沒有就會建立一個新的數組。
例如,假設要儲存部落格文章,要添加一個包含一個數組的"comments"(評論)鍵。可以向還不存在的"comments"數組push一個評論,這個數組會被自動建立,並加入評論:
> db.blog.posts.update({"title" : "A blog post"}, {$push : {"comments" : {"name" : "joe", "email" : "[email protected]", "content" : "nice post."}}})
要是還想添加一條評論,可以接著使用"$push"。
經常會有這種情況,如果一個值不在數組裡面就把它加進去。可以在查詢文檔中用"$ne"來實現。例如,要是坐著不再引文列表中就添加進去,可以這麼做:
> db.papers.update({"authors cited" : {"$ne" : "Richie"}}, {$push : {"authors cited" : "Richie"}})
也可以用"$addToSet"完成同樣的事情,並且"$addToSet"可以避免重複。
> db.users.update({"_id" : ObjectId("4b2d75476cc613d5ee930164")}, {"$addToSet : {"emails" : "[email protected]"}})
將"$addToSet"和"$each"組合起來,可以添加多個不同的值,而用"$ne"和"$push"組合就不能實現。例如:想一次添加多個郵件地址,就可以使用這些修改器:
> db.users.update({"_id" : ObjectId("4b2d75476cc613d5ee930164")},
{"$addToSet" :
"emails" : {"$each" : ["[email protected]", "[email protected]", "[email protected]"]}})
"$pop"修改器可以從數組任何一端刪除元素。{$pop : {key : 1}}從數組末尾刪除一個元素,{$pop : {key : -1}}則從頭部刪除。
"$pull"可以基於特定條件來刪除元素,而不僅僅是一句位置。例如,有一個待完成事項列表,順序有些問題:
> db.list.insert({"todo" : ["dishes", "laundry", "dry cleaning"]})
要是想把洗衣服(alundry)放到第一位,可以從列表中先刪掉:
> db.list.update({}, {"$pull" : {"todo" : "laundry"}})
"$pull"會將所有匹配的部分刪掉。對數組[1, 1, 2, 1]執行pull 1,得到的結果就是只有一個元素的數組[2]。
④數組的定位修改器
若是數組有多個值,而我們只想對其中的一部分進行操作,這就需要一些技巧。有兩種方法運算元組中的值:通過位置或者定位操作符("$")。
數組都是以0開頭的,可以將下表直接作為鍵來選擇元素。例如,這裡有一個文檔,其中包含由內嵌文檔組成的數組,比如包含評論的部落格文章。
> db.blog.posts.findOne()
{
"_id" : ObjectId("4b329a216cc613d5ee930192")
"content" : "..."
"comments" : [
{
"comment" : "good post"
"author" : "John"
"votes" : 0
},
{
"comment" : "i thought it was too short"
"author" : "Claire"
"votes" : 3
}
{
"comment" : "free watches"
"author" : "Alice"
"votes" : -1
}
]
}
如果想增加第一個評論的投票數量,可以這麼做:
> db.blog.update({"post" : post_id}, {"$inc" : {"comments.0.votes" : 1}})
但是很多情況下,不預先查詢文檔就不能知道要修改數組的下標。為了克服這個困難,MongoDB提供了定位操作符"$",用來定位查詢文檔已經匹配的元素,並進行更新。例如,要是使用者John把名字改成Jim,就可以用定位器替換評論中的名字:
> db.blog.update({"comments.author" : "John"}, {"$set" : {"comments.$.author" : "Jim"}})
定位器只更新第一個匹配的元素。
⑤修改器速度
如果修改操作涉及空間分配會減慢修改操作的速度,同事蘇浙數組變長,MongoDB需要更長的時間來遍曆整個數組,對每個數組的修改也會慢下來。
下面的Python程式插入一個鍵,並增加其值100000次(不涉及空間分配):
from pymongo import Connectionimport timedb = Connection().performance_testdb.drop_collection("updates")collection = db.upddatescollection.insert({"x" : 1})# make sure the insert is complete before we start timing collection.find_one()start = time.time()for i in range(100000): collection.update({} : {"$inc" : {"x" : 1}}) # make sure the updates are complete before we stop timingcollection.find_one()print time.time() - start
一共運行了7.33秒。美妙有13000多次更新。
如果是push 100000次的話:
for i in range(100000): collection.update({}, {‘$push‘ : {‘x‘ : 1}})
程式花了67.58秒,每秒更新不到1500次。
『upsert』
upsert是一種特殊的更新。要是沒有文檔符合更新條件,就會以這個條件和更新文檔為基礎建立一個新的文檔。如果找到了匹配的文檔,則正常更新。upsert非常方便,不必預置集合,同一套代碼可以即建立又更新文檔。
回顧之前記錄網站頁面訪問次數的例子。要是沒有upsert,就得試著查詢URL,沒有找到就得建立一個文檔,找到的話就增加訪問次數。要是把這個鞋城JavaScript程式(而不是用mongo scriptname.js來啟動並執行一系列shell命令),會是如下這樣的:
// check if we have an entry for this page
blog = db.analytics.findOne({url : "/blog"})
// if we do, add one to the number of views and save
if (blog) {
blog.pageviews++;
db.analytics.save(blog);
}
// otherwise, create a new document for this page
else {
db.analytics.save({url : "/blog", pageviews : 1})
}
這就是說如果有人訪問頁面,我們得去資料庫打個來回,然後選擇更新或者插入。要是多個進程同時運行這段代碼,還得考慮對於給定URL不能查如文檔的限制。
鑰匙使用upsert,即可以避免競態問題,又可以縮減代碼量(update的第3個參數表示這是一個upsert):
> db.analystics.update({"url" : "/blog"}, {"$inc" : {"visits" : 1}}, true)
這行代碼和之前的代碼作用完全一樣,但它更高效,並且是原子性的。
建立新文檔會將條件文檔作為基礎,然後將修改器文檔應用於其上。例如,要是執行一個匹配鍵並增加對應索引值的upsert操作,會在匹配的基礎上進行增加:
> db.math.remove()
> db.math.update({"count" : 25}, {"$inc" : {"count" : 3}}, true)
> db.math.findOne()
{
"_id" : ObjectId("4b3295f26cc613d5ee93018f"),
"count" : 28
}
先是remove清空了集合,裡面就沒有文檔了。upsert建立了一個鍵"count"的值為25的文檔,隨後將這個值加3,最後得到"count"為28的文檔。要是不開啟upsert選項,{"count" : 25}不會匹配到任何文檔,也就沒有任何更改。
要是將這個upsert(條件為{count : 25})再次運行,還會建立一個新文檔。這是因為沒有文檔匹配條件(唯一的文檔的"count"的值是28)。
save Shell協助程式
save是一個shell函數,可以在文檔不存在時插入,存在時更新。他只有一個參數:文檔。要是這個文檔含有"_id"鍵,save會調用upsert。否則,會調用插入。程式員可以非常方便地使用這個函數在shell中快速修改文檔。
> var x = db.foo.findOne()
> x.num = 42
> db.foo.save(x)
要是不用save,最後一行可以像下面這樣寫,但很囉嗦:
> db.foo.update({"_id" : x._id}, x)
更新多個文檔
預設情況下,更新只能對合格第一個文檔執行操作。要是有多個文檔符合條件,其餘的文檔就沒有變化。要使所有匹配到的文檔都得到更新,可以設定update的第4個參數為true。
多文檔更新對模式遷移非常有用,還可以在對特定使用者發布新功能的時候使用。例如,假設要給所有在特定日期過生日的使用者一份禮物,就可以使用多文檔更新,將"gift"增加到他們的帳號。
> db.users.update({birthday : "10/13/1978"}, {$set : {gift : "Happy Birthday!"}}, false, true)
這樣就給生日為1978年10月113日的所有使用者文檔添加了"gift"鍵。
想要知道多文檔更新到底更新了多少文檔,可以運行getLastError命令。鍵"n"的值就是要的數字。
> db.count.update({x : 1}, {$inc : {x : 1}}, false, true)
> db.runCommand({getLastError : 1})
{
"err" : null,
"updatedExisting" : true,
"n" : 5,
"ok" : true
}
這裡"n"為5,說明5個文檔被更新了。"updatedExisting"為true,說明是對已有的文檔進行更新。
返回已更新的文檔:findAndModify命令。
例:假設我們有一個集合,其中包括以一定順序啟動並執行進程。其中每個進程都被表示為具有如下形式的文檔:
{
"_id" : ObjectId(),
"status" : stat,
"priority" : N
}
"status"是一個字串,可以是"READY"、"RUNNING"或"DONE"。要找到狀態為"READY"的具有最高優先順序的任務,運行進程函數,然後更新其狀態為"DONE"。將已經就緒的進程按照優先順序排序,然後將優先順序最高的進程的狀態更新為"RUNNING"。完成了以後,就把狀態改為"DONE"。
使用"findAndModify"的程式:
> ps = db.runCommand({"findAndModify" : "processes",
"query" : {"status" : "READY"},
"sort" : {"priority" : -1},
"update" : {"$set" : {"status" : "RUNNING"}}}).value
> do_something(ps)
> db.process.update({"_id" : ps._id}, {"$set" : {"status" : "DONE"}})
findAndModify既有"update"鍵也有"remove"鍵。"remove"鍵表示將匹配到的文檔從集合裡面刪除。例如,現在不要更新狀態了,而是直接刪掉,就可以像下面這樣:
> ps = db.runCommand({"findAndModify" : "processes",
"query" : {"status" : "READY"},
"sort" : {"priority" : -1},
"remove" : true}).value
> do_something(ps)
findAndModify命令中每個鍵對應的值如下所示。
· findAndModify
字串,集合名
· query
查詢文檔,用來檢索文檔的條件
· sort
排序結果的條件
· update
修改器文檔,對所找到的文檔執行的更新。
· remove
布爾類型,表示是否刪除文檔。
· new
布爾類型,表示返回的是更新前的還是更新後的文檔。預設是更新前的文檔。
MongoDB學習筆記二:建立、更新及刪除文檔