如果需要在MongoDB中更新文檔,有兩種方式,既可以整個替換文檔,也可以結合一些更新操作符修改文檔中的特定字段。為了替更詳細的例子做些鋪墊,本章會從一個簡單的演示開始,示範這兩種做法。隨後,我會解釋哪種方式更好。
先讓我們回憶一下用戶示例文檔。該文檔包含用戶的姓名、電子郵件地址和送貨地址。毫無疑問,我們時不時會修改一下電子郵件地址,因此就從它開始吧。要完整替換文檔,先查詢該文檔,隨後在客戶端進行修改,最後用修改後的文檔發起更新。以下是對應的Ruby代碼:
user_id = BSON::ObjectId(\"4c4b1476238d3b4dd5000001\") doc = @users.find_one({:_id => user_id}) doc[\'email\'] = \'[email protected]\' @users.update({:_id => user_id}, doc, :safe => true)
有了用戶的_id
,可以先查詢文檔。接下來在本地進行修改,這裡是修改email
屬性。隨後將修改過的文檔傳給update
方法。最後一行的意思是「找到users
集合中指定_id
的文檔,用我提供的文檔替換它」。
以上展示了如何通過替換進行修改,現在讓我們看看如何通過操作符進行修改:
@users.update({:_id => user_id}, {\'$set\' => {:email => \'[email protected]\'}}, :safe => true)
本例中使用$set
在一個服務器請求裡修改了電子郵件地址,這是多個特殊更新操作符中的一個。這裡的更新請求更有針對性:找到指定用戶文檔,將其email
字段設置為[email protected]
。
其他示例又會如何?這次,我們向用戶的地址列表中添加其他送貨地址,下面展示如何通過文檔替換實現該操作:
doc = @users.find_one({:_id => user_id}) new_address = { :name => \"work\", :street => \"17 W. 18th St.\", :city => \"New York\", :state => \"NY\", :zip => 10011 } doc[\'shipping_addresses\'].append(new_address) @users.update({:_id => user_id}, doc)
更有針對性的做法是這樣的:
@users.update({:_id => user_id}, {\'$push\' => {:addresses => {:name => \"work\", :street => \"17 W. 18th St.\", :city => \"New York\", :state => \"NY\", :zip => 10011 } } })
替換的方法與之前類似,從服務器獲取用戶文檔,進行修改,隨後發回服務器。此處的更新語句和更新電子郵件地址時的一樣。相比之下,針對性更新裡使用了不同的操作符$push
,將新地址推送到現有的shipping_addresses
數組裡。
既然已經看過了幾個實際的更新,請思考一下,已經有了一種方法後為什麼還要用另一種呢?你覺得哪種方式更直觀,哪種方式的性能會更好?
替換更新是種更通用的方式。假設應用程序顯示了一個用於更新用戶信息的HTML表單,使用替換更新時,從表單提交的數據一經校驗就能直接傳入MongoDB;無論修改了哪個用戶屬性,執行更新的代碼都是一樣的。舉例來說,如果你打算構建一個MongoDB對像映射器,需要通用的更新,那麼替換更新可能更適合作為默認值。1
1. 大多數MongoDB對像映射器都採用這種策略,原因也很簡單。如果用戶可以建模任意複雜度的實體,那麼發起替換更新比計算特殊更新操作符的理想組合要方便得多。
針對性更新通常性能會更好。首先,不需要在開始時到服務器上獲取要修改的文檔。其次,指定更新內容的文檔一般都很小。如果是通過替換進行更新,文檔的平均大小是100 KB,那麼每次更新都要向服務器發送100 KB內容!相比之下,上個例子裡,無論要修改的文檔有多大,每個使用$set
和$push
來指定更新的文檔都小於100字節。為此,經常使用針對性更新就意味著節省序列化和傳輸數據的時間。
此外,針對性操作允許原子性地更新文檔。舉例來說,如果需要增加計數器值,通過替換進行更新就很不理想;唯一能對它們進行原子性更新的方法就是採用某類樂觀鎖。在針對性更新中,可以使用$inc
原子性地修改計數器。也就是說,就算有大量的並發更新,每次執行$inc
都是相互隔離的,要麼成功,要麼失敗。2
2. MongoDB文檔中使用原子更新(atomic update)這個詞來表示我所說的針對性更新(targeted update)。這個新術語意在突出原子這個詞。實際上,所有發往核心服務器的更新都是原子性的,以文檔為單位進行隔離。說更新操作符是原子性的是因為它們能在不用先查詢的情況下更新文檔。
樂觀鎖
樂觀鎖即樂觀並發控制,這項技術保證在無需鎖定記錄的情況下對其進行徹底更新。要理解它,最簡單的方法是想像一個wiki,有多個用戶可以同時編輯一個wiki頁面,但你肯定不希望用戶編輯並更新一個過期的頁面,這時可以使用樂觀鎖協議。當用戶試圖保存他們的變更時,會在更新操作中包含一個時間戳,如果該值比這個頁面最近保存的版本舊,那麼不能讓用戶進行更新;但如果沒人修改過這個頁面,則允許更新。該策略允許多個用戶同時編輯一個頁面,比另一種要求每個用戶在編輯任意頁面時獲得一個鎖的並發策略要好很多。
知道了可用的更新種類之後,你就能理解下一節裡我將介紹的策略了。下一節中,我們會回到電子商務數據模型,回答一些與在生產環境中操作數據相關的、更困難的問題。