首先列一下WriteConcern的幾種拋出異常的級別參數:
- WriteConcern.NONE:沒有異常拋出
- WriteConcern.NORMAL:僅拋出網絡錯誤異常,沒有服務器錯誤異常
- WriteConcern.SAFE:拋出網絡錯誤異常、服務器錯誤異常;並等待服務器完成寫操作。
- WriteConcern.MAJORITY: 拋出網絡錯誤異常、服務器錯誤異常;並等待一個主服務器完成寫操作。
- WriteConcern.FSYNC_SAFE: 拋出網絡錯誤異常、服務器錯誤異常;寫操作等待服務器將數據刷新到磁盤。
- WriteConcern.JOURNAL_SAFE:拋出網絡錯誤異常、服務器錯誤異常;寫操作等待服務器提交到磁盤的日誌文件。
- WriteConcern.REPLICAS_SAFE:拋出網絡錯誤異常、服務器錯誤異常;等待至少2臺服務器完成寫操作。
當我們執行如下操作時(將"name"爲"lily"的"age"設置爲20):
db.update({"name":"lily"},{"$set":{"age":20}})
默認情況下,該操作會使用WriteConcern.NORMAL(僅在網絡錯誤時拋出異常),等同於:
db.update({"name":"lily"},{"$set":{"age":20}},WriteConcern.NORMAL)
使用NORMAL模式參數,可以使得寫操作效率非常高。但是如果此時服務器出錯,也不會返回錯誤給客戶端,而客戶端會誤認爲操作成功。
因此在很多重要寫操作中需要使用WriteConcern.SAFE模式,保證可以感知到這個錯誤,保證客戶端和服務器對一次操作的正確性認知保持一致。
(根據筆者測試,如果服務器發生掉電情況,客戶端依然得不到當時操作的錯誤返回,需要特別注意)
另外在很多時候,我們需要確切知道這次寫操作是否成功(或者本次更新操作影響了多少個對象),這時候就需要:
WriteResult ret = db.update({"name":"lily"},{"$set":{"age":20}}); if(ret.getN()>0) //操作影響的對象個數 return true; else return false;
或者:
WriteResult ret = db.update({"name":"lily"},{"$set":{"age":20}}); if(ret.getLastError() == null) return true; else return false;
此時,getLastError()會查詢上次操作結果是否出現錯誤。
更進一步
然後由於mongodb中使用連接池的原因,getLastError()需要再次從連接池中獲取連接,這樣效率會慢一些。可以這樣做:
db.requestStart(); WriteResult ret = db.update({"name":"lily"},{"$set":{"age":20}}); if(ret.getLastError() == null) return true; else return false; db.requestDone();
就可以保證update操作和getLastError()使用同一個連接,並且減少了一次存/取連接的過程。
還有一個方法
此時也可以使用WriteConcern.SAFE參數:
WriteResult ret = db.update({"name":"lily"},{"$set":{"age":20}}, WriteConcern.SAFE); if(ret.getLastError() == null) return true; else return false; // is equivalent to db.requestStart(); WriteResult ret = db.update({"name":"lily"},{"$set":{"age":20}}); if(ret.getLastError() == null) return true; else return false; db.requestDone();
這也是我推薦使用的方式,這樣即可以高效的得到返回結果,還能感知到服務器錯誤,一舉兩得。