2015年1月22日 星期四

5.備份及還原MongoDB(mongodump , mongorestore)


MongoDB提供了2個指令 mongodump , mongorestore 來完成我們日常的備份及還原操作。
這2個指令基本上是以 db 為單位的,如果要單一備份一個Collection的話,可以 mongoexport 指令來完成;當然mongodump加上 c 參數也可以,不過我覺得還是區別化的使用可能比較好。今天主要介紹mongodump , mongorestore為主,這2個指令都存在於Mongo的資料夾的Bin目錄中。

備份資料庫 ( Mongodump )

mongodump基本的運作原理是先執行查詢動作之後,並把所有查詢結果寫入硬碟中,但問題出現了因為基本上可能有很多文檔資料還在記憶體中未寫入硬碟中,或是你在執行備份的同時仍可能有很多新增、修改操作也正在運行中,這時侯你dump出來的資料就非常可能是一個不完整備份,在本文的最後會紅有推荐的方式來處理這一個問題,我們先下看一下如何基本的使用mongodump先。

執行 mongodump --help 列出所有參數說明,如下圖




常用的參數:

-h:         host,表示資料庫主機的IP位置或是電腦名稱
-u:         user,登入的帳號
-p:         password,登入的密碼
--port:  指定port號
-d:        database,備份資料庫的名稱
-c:        collection,指定備份某一個Collection
-o:        output備份輸出的磁碟位置(Folder Path)
-q:        query,備份的過濾條件(基本上我沒有使用過這一個參數 ^ ^ )


使用範列

  • 我要備份本機的mydb的全部資料,則可以使用以下指令
    • mongodump -d mydb -o ../data/backup
  • 我要備份遠端的資料庫的話,就要加入 -h , -u , -p , --port 等參數,可能的指令如下
    • mongodump -h myserver2 --port 37017 -u myname -p xxxx -d mydb -o ../data/backup


★會紅比較推荐的備份方式:


在備份前先執行 fsynclock 指令,讓在記憶體中的資料同步到磁碟中,並且先把這一個資料庫上鎖,防止寫入的操作,再執行備份操作,最後一定要記得解鎖,雖然同步是多了一點但可以確保資料庫的完整性。

1.先切換到admin資料庫中
   use admin

2.執行磁碟同步與上鎖工作
   db.runCommand({fsync:1,lock:1})




3.查看一下是不是真的上鎖了
   db.currentOp()


4.先開出另一個console視窗來執行備份操作,如下圖

5.備份完成後記得要解鎖

db.fsyncUnlock()




還原資料庫 ( Mongorestore )

mongorestore的參數跟mongodump參數差不多,有一個比較要特別注意就是 --drop
我們先使用mongorestore --help 列出所有的參數列表




注意: --drop 參數表示在還原操作前會先把每一個collection先刪除,一般的狀態之下是要加入這一個參數。


還原範例:

mongorestore -d yourDataBaseName --drop d:/backup/yourDataBaseName 




今天的內容比較容易我應該可以不用錄製教學視頻吧,^ ^ … 。

2015年1月13日 星期二

6.MongoDB的條件操作符(2)

教程視頻:將於1/30日晚間發佈視頻。

在MongoDB的條件操作符(1)介紹已經提出許多操作能力,但是好像還沒有介紹完成,所以在本文中主要補允未說明的指令為主。

在操作之前我需要為大家先準備好一些資料,以便後續的操作又及理解。

建立一個 Collection 名為 bookStore (書店)

1.先切換到 mydb 資料庫
  use mydb   

2.執行資料插入
 db.bookStore.insert([{    
   name: "storeA",    
   books: ["小說", "漫書", "商業期刊", "雜誌"],    
   startYear: 2015,    
   region: "台北"    
  }, {    
   name: "storeB",    
   books: ["商業期刊", "漫書", "雜誌"],    
   startYear: 2015,    
   region: "台北"    
  }, {    
   name: "storeC",    
   books: ["雜誌", "商業期刊"],    
   startYear: 2015,    
   region: "新竹"    
  }, {    
   name: "storeD",    
   books: ["小說", "漫書", "商業期刊", "雜誌"],    
   startYear: 2012,    
   region: "新竹"    
  }, {    
   name: "storeE",    
   books: ["商業期刊", "雜誌"],    
   startYear: 2011,    
   region: "台中"    
  }, {    
   name: "storeF",    
   books: ["漫書", "商業期刊"],    
   startYear: 2014,    
   region: "台中"    
  }, {    
   name: 123,    
   books: ["漫書", "商業期刊"],    
   startYear: 2014,    
   region: "台中"    
  }])  

3.驗証一下資料是否存在

  db.bookStore.find().count()  

是不是輸出有7筆資料。

現在我們的資料已經準備好了,就可以來練習find的語法。

$all : 匹配指定內容
比如說我們要找那些書店 有賣"商業期刊"而且也有"漫書" ?

  db.bookStore.find({    
   books: {    
   $all: ["商業期刊", "漫書"]    
   }    
  }).count() 
返回5筆資料

與下段程式是功能是相同的

  db.bookStore.find({    
   $and: [{    
   books: {    
    $in: ["漫書"]    
   }    
   }, {    
   books: {    
    $in: ["商業期刊"]    
   }    
   }]    
  }).count()   
返回5筆資料

但是我們在下語句,常常下成這樣,看似相同但是結果大不同,因為以下的
語句返回了 7 筆資料…,這是因為 他的查詢方式有關。

  db.bookStore.find({    
   books: {    
   $in: ["漫書","商業期刊"]    
   }    
  }).count() 
返回7筆資料

$mod:取MOD後查詢
說白話點就是先經由 取餘運算之後的結果 再來查詢啦!
以下語句是用來查找startYear 除 10 後等於1的資料。

  db.bookStore.find({    
   startYear: {    
   $mod: [10, 1]    
   }    
  })  




$size:取得node數量、長度
以下語句是用來 查找只賣2書籍的書店有幾家?

  db.bookStore.find({    
   books: {    
   $size: 2    
   }    
  })   



$type :指定資料的類型來查詢
以下語句是用來 查詢 name 欄位型別為 double 的書店?

  db.bookStore.find({    
   name: {    
   $type: 1    
   }    
  })   

type對應類型表如下:


$elemMatch : 資料符合查詢
在練習elemMatch指令之前我們需要先修改一下原來的資料,為每一間書店加上店員資訊。
這邊將使用到 更新操作符 $SET , 這次最主要是更新新增一個欄位為 employee 表示店員資訊;而employee 是一個Array,儲存著店員相關資料(name,age,salary)。

簡單的來說 elemMatch指令 是可以對內含的對象再進行條件查詢使用的。

以下是更新語句

 db.bookStore.update({name:"storeA"},{   
    $set:{   
       employee:[   
         {name:'小王',age:18,salary:100},   
         {name:'小李',age:19,salary:110}   
       ]   
    }   
  });   
  db.bookStore.update({name:"storeB"},{   
    $set:{   
       employee:[   
         {name:'小陳',age:20,salary:100},   
         {name:'小劉',age:21,salary:110}   
       ]   
    }   
  });   
  db.bookStore.update({name:"storeC"},{   
    $set:{   
       employee:[   
         {name:'小一',age:22,salary:100},   
         {name:'小二',age:21,salary:110}   
       ]   
    }   
  });   
  db.bookStore.update({name:"storeD"},{   
    $set:{   
       employee:[   
         {name:'張三',age:23,salary:100},   
         {name:'李四',age:25,salary:110}   
       ]   
    }   
  });   
  db.bookStore.update({name:"storeE"},{   
    $set:{   
       employee:[   
         {name:'老王',age:24,salary:100},   
         {name:'趙六',age:23,salary:110}   
       ]   
    }   
  });   
  db.bookStore.update({name:"storeF"},{   
    $set:{   
       employee:[   
         {name:'王九',age:26,salary:100},   
         {name:'黃十',age:18,salary:110}   
       ]   
    }   
  });   
  db.bookStore.update({name:123},{   
    $set:{   
       employee:[   
         {name:'大王',age:31,salary:100},   
         {name:'大李',age:20,salary:110}   
       ]   
    }   
  });   

我們現在需要查出那一個書店有18歲的店員,可以使用下列語句

  db.bookStore.find({   
    employee:{   
       $elemMatch:{   
         age:18   
       }   
    }   
  }) 

我們現在需要查出那一個書店的店員年齡大於等於30歲,可以使用下列語句

  db.bookStore.find({   
    employee:{   
       $elemMatch:{   
         age:{ $gte:30 }   
       }   
    }   
  })   

$slice :針對array 元素進行切片操作
取得所有書店的第一本書
  db.bookStore.find({}, {  
   books: {  
    $slice: [1, 1]  
   },  
   _id: 0,  
   name: 1  
  }).pretty()  

執行結果


limit() 限制取得資料數量、skip() 跳過多少筆資料

一般來說功能用來實現分頁的功能,因為這一個功能比較easy…
大家直接看執行語句與執行結果就可以了解。
1.首先執行

 db.bookStore.find({}, {  
  name: 1,  
  _id: 0  
 })  

執行結果:查到7筆資料



2.使用skip指令

 db.bookStore.find({}, {   
  name: 1,   
  _id: 0   
  }).skip(1)  

執行結果:查到6筆資料


3.使用limit指令(限制資料量)


  db.bookStore.find({}, {  
   name: 1,  
   _id: 0  
  }).skip(1).limit(2) 

執行結果:查到2筆資料


下節教程內容為 MongoDB 的條件操作符(3)就是進階的查詢 ^ ^

2015年1月12日 星期一

4.MongoDB的條件操作符(1)

教程視頻:
                


  • 結文結構:
    • 1.MongoDB常用的查詢條件介紹
    • 2.使用find指令範例(基礎)
    • 3.條件操作符的使用


在介紹『條件操作符』之前先要了解一下 find 指令,我們來看一下 find 指令的結構。

 db.collection.find(  
   {條件},  
   {鍵指定}  
 )  

參數說明
    條件    :條件操作符物件
    鍵指定:想要顯示的欄位


在使用 find 前我們需要準備一些資料,以利學習,我們要下載一下範本的資料表,然後使用mongoimport指令將資料匯入我們的資料庫中。

操作步驗如下:

1.下載 JSON file  (http://media.mongodb.org/zips.json)
2. 匯入資料
    mongoimport.exe --db mydb  --file ../zips.json
    
   connected to: 127.0.0.1  
   no collection specified!  
   using filename 'zips' as collection.  
   2015-01-12T09:30:55.804+0800 check 9 29353  
   2015-01-12T09:30:55.811+0800 imported 29353 objects  

3.當出現imported 29353 時則表示資料已成功匯入了,我們再使用
   mongo指令連接到你的server中查到一下。

   3.1 使用use mydb 切換資料庫
   3.2 使用 db.zips.find().count() 是否有輸出有 29353 筆資料


*使用find指令範例(基礎)
    1.查詢全部的資料

     db.zips.find()
       或是
       db.zips.find({ })

      查詢結果如下圖
  

       注意:mongo在查詢資料時侯是有限制顯示筆數。有像是20筆,
                  如你要看下一頁的資料請鍵入 it 後按下 Enter 鍵即可,查看下一頁。

  2.限制顯示的欄位
       這一個操作就像是SQL中的顯示數位


     SELECT CITY FROM ZIPS  

      在find的指令的第2個引數就是來完成相同的操作,但有一點必須注意就
      是 _id 是預設顯示的,如果你不想看見的話,把他設定成 0 。 如下操作



     db.zips.find({},{_id:0 , city:1}) 

      * 0 表示不要顯示此欄位
         1 表示要顯示此欄位

      查詢結果如下圖
      
3.限制顯示的欄位 + 排序
     使用sort指令直接連綴在find指令之後,就像是JQuery的語法。
     sort的格式為
    ( {欄位:排序方式} )  

     *排序方式 等於 『  1 』時表示正向排序就是SQL中的ASC

     *排序方式等於  『- 1』時表示反向排序就是SQL中的DESC


 db.zips.find({},{city:1,_id:0}).sort({city:1})  

     查詢結果如下圖
    

現在大家都應該了解基本的find指令的運用了吧,接下來就是條件操作符的使用,以下內文擷於http://docs.mongodb.org/manual/reference/operator/query/,在這篇文章的內容說真的要詳細說明的話要花費不少的時間,所以我只針對find指令會用到的條件操作符做說明( Comparison , Logical , Element , Evluation),另外黃底部份是我額外的說明或程式碼範例。

Comparison

For comparison of different BSON type values, see the specified BSON comparison order.
NameDescription
$gtMatches values that are greater than the value specified in the query.
大於 ( > )
使用範例:
  db.zips.find(  
   {  
     pop: { $gt:100000 }  
   }).count() 
將返回 4 筆資料
$gteMatches values that are greater than or equal to the value specified in the query.
大於等於 ( >= )使用範例: 
 db.zips.find(  
   {  
     pop: { $gte:100000 }  
   }).count() 
將返回 4 筆資料
$inMatches any of the values that exist in an array specified in the query.
包含 使用範例:
 db.zips.find(  
   {  
     city: { $in:['NEW YORK'] }  
   }).count()  
將返回 40 筆資料
$ltMatches values that are less than the value specified in the query.
小於  ( < )
使用範例:
db.zips.find(
    {
        pop:  {  $lt:100  }
    }).count()
將返回 701 筆資料
$lteMatches values that are less than or equal to the value specified in the query.
小於等於  ( <= )
使用範例:
 db.zips.find(  
   {  
     pop: { $lt:100 }  
   }).count() 
將返回 706 筆資料
$neMatches all values that are not equal to the value specified in the query.
不等於 ( != )
使用範例:
 db.zips.find(  
   {  
     city: { $ne:'NEW YORK' }  
   }).count()  
將返回 29313 筆資料
$ninMatches values that do not exist in an array specified to the query.
不包含使用範例:
 db.zips.find(  
   {  
     city: { $nin:['NEW YORK'] }  
   }).count() 
將返回 29313 筆資料

Element

Logical

NameDescription
$andJoins query clauses with a logical AND returns all documents that match the conditions of both clauses.
並且 ( && )
db.zips.find({
   $and:[
              {city:"AGAWAM"},
              {pop:15338}
            ]
})
$norJoins query clauses with a logical NOR returns all documents that fail to match both clauses.
或取反 ! ( or ) ,也就是找不符合條件的資料
 db.zips.find({  
   $nor: [{  
     city: 'NEW YORK'  
   }, {  
     city: 'CHICAGO'  
   }]  
 }).count()  
將返回 87 筆資料
$notInverts the effect of a query expression and returns documents that do not match the query expression.
返選操作
*取得city 不包含 NEW 字眼的所有城市
 db.zips.find({  
   city: {  
     $not: /NEW/i  
   }  
 }).count()  
將返回 28879 筆資料

$orJoins query clauses with a logical OR returns all documents that match the conditions of either clause.
或  ( or )
 db.zips.find({  
   $or: [{  
     city: 'NEW YORK'  
   }, {  
     city: 'CHICAGO'  
   }]  
 }).count()  

Evaluation

NameDescription
$modPerforms a modulo operation on the value of a field and selects documents with a specified result.
$regexSelects documents where values match a specified regular expression.
使用正則式查詢
 db.zips.find({  
   city: /^NEW/i  
 }).count()  
$textPerforms text search.
$whereMatches documents that satisfy a JavaScript expression.
進階的查詢功能,一但使用到這一個功能查詢的速度可以會下降,但他的功能算是更強大的
db.zips.find({
            $where: function() {
             if(this.state=='MA' && this.city.indexOf('EAST') >= 0 ){
                return true;
             }
             if(this.state=='WA' && this.city.indexOf('EAST') >= 0 ){
                return true;
             }
             return false;
            }
        })
返回的資料筆數為 18 筆 。

NameDescription
$existsMatches documents that have the specified field.
欄位存在檢查操作
我們先執行一個更新操作



 db.zips.update({  
   _id: "01001"  
 }, {  
   $set: {  
     test: 'test'  
   }  
 }) 

我們必須先更新 _id 為 01001 這一筆資料,插入一個新的欄位為

test;接下來我們可以用測式exists指令了,我們要找有test欄位的
文檔就可以透過下列指令完成。


 db.zips.find({  
   test: { $exists : 1 }  
 }) 

返回資料為:
{ "_id" : "01001", "city" : "AGAWAM", "loc" : [ -72.622739, 42.070206 ], "pop" :
 15338, "state" : "MA", "test" : "test" }

$typeSelects documents if a field is of the specified type.
請參考 http://docs.mongodb.org/manual/reference/operator/query/type/ ,因為這一個操作比較少使用,我就不多說了。

最後出幾個綀習題

1.在zips中查出不重覆的city名稱,並且使用city作排序由小到大
db.zips.distinct("city").sort({city:1})


2.在zips中查出status 為 WA 或 AL 的所有城市名稱?
> db.zips.find({ state :{$in:['WA','AL']}  }).count()

3.在zips中查出_id值大於等於10000 並且 小於等於 100100 的city有那一些?
> db.zips.find({ $and:[{ _id : { $gt:'10000' }},{_id:{$lt:'100100'}}]  })

4.使用正則式找到 city 以EAST 開頭 ,並且結尾字母為N的城市?
> db.zips.find({city:/^EAST.+N$/i}).count()

PS:不熟悉正則表達式的人可以 參考一下這篇文章
      http://openhome.cc/Gossip/JavaGossip-V1/RegularExpression.htm

下節教程內容為 MongoDB的條件操作符(2)

2015年1月8日 星期四

3.MongoDB基本操作(Insert document , remove document, drop collection)

  • Document 文件插入

教程視頻



  • 本文結構:
    • 1.基本的插入語句結構
    • 2.基本插入+ writeconcern
    • 3.批量插入文檔 有可能使用 shell 來完成嗎?
    • 4.insert 與 save操作區別
    • 5.Document 文件刪除
    • 6.使用justOne參數,來刪除一筆資料


1.基本的插入語句結構  

db.collection.insert(
   <documents>,
   {
     writeConcern: {
         w:"majority",
         wtimeout:5000
     },
     ordered: <boolean>
   }
)
參數說明

       * <document> 插入的文檔物件
       * writeConcern:文檔寫入等級設定,預設等級設 w:1
          分成5點等級
             
               w:-1     (嚴謹性:非常低);發生資料庫寫入錯誤一慮不回傳
               w:0      (嚴謹性:低)       ;只可以偵測到網路錯誤
               w:1      (嚴謹性:中 預設等級)
               w:1,j:1 (嚴謹性:高)       ;在寫入日誌後才回傳處理過程。
               w:2      (嚴謹性: 高 )     ;這個級別只在replica set的部署模式下生效
           
            備註:w為設定寫入等級 ; j為日誌設定

            關於writeConcern的選項大家可以看一下這篇文章寫的很清楚
            http://kyfxbl.iteye.com/blog/1952941

基本的插入範例

db.employee.insert({
   name:'canred',
   account:'canred',
   country:'tw',
   age:30
})

操作過程1
 > use employee  
 switched to db employee  
 > db.employee.insert({name:'Canred',account:'Canred',country:'tw',age:30})  
 WriteResult({ "nInserted" : 1 })  
 > db.employee.insert({name:'emp1',account:'emp1',country:'us',age:28})  
 WriteResult({ "nInserted" : 1 })  
 > db.employee.insert({name:'emp2',account:'emp2',country:'us',age:28})  
 WriteResult({ "nInserted" : 1 })  
 > db.employee.insert({name:'emp3',account:'emp3',country:'us',age:28})  
 WriteResult({ "nInserted" : 1 })  
 > db.employee.find().pretty()  
 {  
     "_id" : ObjectId("54af3572540eca3abfc91415"),  
     "name" : "Canred",  
     "account" : "Canred",  
     "country" : "tw",  
     "age" : 30  
 }  
 {  
     "_id" : ObjectId("54af358a540eca3abfc91416"),  
     "name" : "emp1",  
     "account" : "emp1",  
     "country" : "us",  
     "age" : 28  
 }  
 {  
     "_id" : ObjectId("54af3591540eca3abfc91417"),  
     "name" : "emp2",  
     "account" : "emp2",  
     "country" : "us",  
     "age" : 28  
 }  
 {  
     "_id" : ObjectId("54af3598540eca3abfc91418"),  
     "name" : "emp3",  
     "account" : "emp3",  
     "country" : "us",  
     "age" : 28  
 }  
 >  

基本插入+ writeconcern 範例
注意當你使用writeConcern的j:1的時侯,要先確定你用啟動mongod 要有先啟動日誌功能。
ex: mongod --dbpath ../data/mydb --journal

db.employee.insert({
   name:'canred',
   account:'canred',
   country:'tw',
   age:30
},{
   writeConcern:{
       w:1,
       j:1
   }
})

2.批量插入文檔 有可能使用 shell 來完成嗎?


    基本上我們想想應該與 插入單個文檔 相同,最多是 來 [ ] 陣列的符號來操作,沒錯就是
    這樣,當然也可以使用 for 語句來 批量插入。

 db.employee.insert(   
   [   
   {name:"Data1"},   
   {name:"Data2"},   
   {name:"Data3"}   
   ])  

    然後我們在使用 db.employee.find() 來看一下資料是不是多了3筆。



3.insert 與 save操作區別


   若存在主鍵,insert() 不做操作,而save() 則更改原來的內容為新內容
   若存在數據有一筆  { _id : 1, " name " : " n1 " }
    insert({ _id : 1, " name " : " n2 " })           //會提示錯誤
    save({ _id : 1, " name " : " n2 " })            //會把n1 改為n2
    save 操作在 _id  找的得的狀況下,會執行更新操作;
    若 _id 找不到的時侯會執行插入操作。

操作過程
 > db.employee.insert({_id:'A',name:'Hello'})  
 WriteResult({ "nInserted" : 1 })  
 > db.employee.insert({_id:'A',name:'Hello'})  
 WriteResult({  
     "nInserted" : 0,  
     "writeError" : {  
         "code" : 11000,  
         "errmsg" : "insertDocument :: caused by :: 11000 E11000 duplicat  
 e key error index: employee.employee.$_id_ dup key: { : \"A\" }"  
     }  
 })  
 > db.employee.save({_id:'A',name:'Hello 2'})  
 WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })  
 > db.employee.find()  
 { "_id" : ObjectId("54af3572540eca3abfc91415"), "name" : "Canred", "account" : "  
 Canred", "country" : "tw", "age" : 30 }  
 { "_id" : ObjectId("54af358a540eca3abfc91416"), "name" : "emp1", "account" : "em  
 p1", "country" : "us", "age" : 28 }  
 { "_id" : ObjectId("54af3591540eca3abfc91417"), "name" : "emp2", "account" : "em  
 p2", "country" : "us", "age" : 28 }  
 { "_id" : ObjectId("54af3598540eca3abfc91418"), "name" : "emp3", "account" : "em  
 p3", "country" : "us", "age" : 28 }  
 { "_id" : ObjectId("54af3960540eca3abfc9141f"), "name" : "Data1" }  
 { "_id" : ObjectId("54af3960540eca3abfc91420"), "name" : "Data2" }  
 { "_id" : ObjectId("54af3960540eca3abfc91421"), "name" : "Data3" }  
 { "_id" : "A", "name" : "Hello 2" }  
 > db.employee.save({_id:'B',name:'Hello 3'})
WriteResult({ "nMatched" : 0, "nUpserted" : 1, "nModified" : 0, "_id" : "B" })


  • Document 文件刪除


1.刪除文檔的語句結構  

db.collection.remove( {條件式},justOne )

參數說明


    * {條件式} :查詢條件

    * justOne :true | false ,設定是否只刪除第一筆文檔

刪除所有文檔範例

可以使用 { } 來表示空的條件 ,也就是表示全部
db.employee.remove({}


根據修件刪除文檔

db.employee.remove( {name:'Canred'} 
在文檔中 若 name 為 Canred ,將全部刪除

使用justOne參數,來刪除一筆資料

db.employee.remove( {name:'Canred'} , true 


  • 刪除collection


使用drop即可完成

1.刪除collection的語句結構   

    db.collection.drop()





參考資料:來源於 http://kyfxbl.iteye.com/blog/1952941

mongodb有一個write concern的設置,作用是保障write operation的可靠性。一般是在client driver裡設置的,和db.getLastError()方法關係很大
一般來說,所有的mongo driver,在執行一個寫操作(insert、update、delete)之後,都會立刻調用db.getLastError()方法。這樣才有機會知道剛才的寫操作是否成功,如果捕獲到錯誤,就可以進行相應的處理。處理邏輯也是完全由client決定的,比如寫入日誌、拋出錯誤、等待一段時間再次嘗試寫入等。作為mongodb server並不關心,server只負責通知client發生了錯誤
這裡有2點需要注意:
1、db.getLastError()方法是由driver負責調用的,所以業務代碼不需要去顯式調用。這點後面還會專門提到
2、driver一定會調用db.getLastError()函數,但是並不一定能捕獲到錯誤。這主要取決於write concern的設置級別,這也是本文的主題

write concern:0(Unacknowledged)

此級別調用的時序圖如下:
driver調用了getLastError()之後,mongod立刻返回結果,然後才實際進行寫操作。所以getLastError()的返回值一定是null,即使之後的Apply發生了錯誤,driver也不知道。使用這個級別的write concern,driver的寫入調用立刻返回,所以性能是最好的,但是可靠性是最差的,因此並不推薦使用。在各平台最新版本的driver中,也不再以0作為默認級別。其實還有一個w:-1的級別,是error ignored,基本上和w:0差不多。區別在於,w:-1不會捕獲任何錯誤,而w:0可以捕獲network error

write concern:1(acknowledged)

此級別調用的時序圖如下:
和Unacknowledged的區別是,現在mongod只有在Apply(實際寫入操作)完成之後,才會返回getLastError()的響應。所以如果寫入時發生錯誤,driver就能捕獲到,並進行處理。這個級別的write concern具備基本可靠性,也是目前mongodb的默認設置級別

write concern:1 & journal:true(Jounaled)

此級別調用的時序圖如下:

Acknowledged級別的write concern也不是絕對可靠的。因為mongodb的Apply操作,是將數據寫入內存,定期通過fsync寫入硬盤。如果在Apply之後,fsync之前mongod掛了,或者甚至server掛了,那持久化實際上是失敗的。但是在w:1的級別下,driver無法捕獲到這種情況下的error(因為response在apply之後就已經返回到driver)
mongod解決這個問題的辦法是使用Journal機制,寫操作在寫入內存之後,還會寫到journal文件中,這樣如果mongod非正常down掉,重啟以後就可以根據journal文件中的內容,來還原寫操作。在64位的mongod下,journal默認是打開的。但是32位的版本,需要用--journal參數來啟動
在driver層面,則是除了設置w:1之外,再設置journal:true或j:true,來捕獲這個情況下的error

write concern:2(Replica Acknowledged)

這個級別只在replica set的部署模式下生效

這個級別下,只有secondary從primary完成了複製之後,getLastError()的結果才會返回。也可以同時設置journal:true或j:true,則還要等journal寫入也成功後才會返回。但是注意,只要primary的journal寫入就會返回,而不需要等待secondary的journal也寫入。類似的也可以設置w:3,表示至少要有3個節點有數據;或者w:majority,表示>1/2的節點有數據。一般小規模的集群就是3節點部署,所以配置w:2就可以了

建議

設置write concern級別,其實就是在寫操作的性能和可靠性之間做權衡。寫操作的等待時間越長,可靠性就越好。對於非關鍵數據,建議使用默認的w:1就可以了,對於關鍵數據,則使用w:1 & j:true比較好。這裡要注意,journal無論如何都是建議打開的,設置j:true,只是說driver調用getLastError()之後是否要等待journal寫入完成再返回。並不是說不設置j:true就關閉了server端的journal