Mongodb中的 原子性 隔離性


讀寫鎖

Mongodb使用讀寫鎖來來控制並發操作:

當進行讀操作的時候會加讀鎖,這個時候其他讀操作可以也獲得讀鎖。但是不能或者寫鎖。

當進行寫操作的時候會加寫鎖,這個時候不能進行其他的讀操作和寫操作。

所以按照這個道理,是不會出現同時修改同一個文檔(如執行++操作)導致數據出錯的情況。

而且按照這個道理,因為寫操作會阻塞讀操作,所以是不會出現臟讀的。

但是mongodb在分片和復制集的時候會產生臟讀,后面在研究。

讀寫鎖的粒度:

在2.2之前的版本,一個mongodb實例一個寫鎖,多個讀鎖,在2.2-3.0的版本,一個數據庫一個寫鎖,多個讀鎖,在3.0之后的版本,WiredTiger提供了文檔(不是集合)級別的鎖。

 

findAndModify

 

db.collection.findAndModify({

query: <document>,

sort: <document>,

remove: <boolean>,

update: <document>,

new: <boolean>,

fields: <document>,

upsert: <boolean>,

bypassDocumentValidation: <boolean>,

writeConcern: <document>,

collation: <document>

});

documentsort:

。可選的。以此參數指定的排序順序修改第一個文檔。

document

document。可選的。 要返回的字段的子集。 如:fields: {<field1>: 1, <field2>: 1, ... }

book = {

_id: 123456789,

title: "MongoDB: The Definitive Guide",

author: [ "Kristina Chodorow", "Mike Dirolf" ],

published_date: ISODate("2010-09-24"),

pages: 216,

language: "English",

publisher_id: "oreilly",

available: 3,

checkout: [ { by: "joe", date: ISODate("2012-10-15") } ]

}

你可以使用 db.collection.findAndModify() 方法來判斷書籍是否可結算並更新新的結算信息。

在同一個文檔中嵌入的 available  checkout 字段來確保這些字段是同步更新的:

db.books.findAndModify ( {

query: {

_id: 123456789,

available: { $gt: 0 }

},

update: {

$inc: { available: -1 },

$push: { checkout: { by: "abc", date: new Date() } }

}

} )

 

執行多個寫入操作

 

首先,原則上說Mongdb沒有事務的概念。

事務有ACID的概念,比如原子性,一個事務要么全部成功,要么全部失敗。

如,考慮一個轉賬的業務,從A轉賬100到B,將分為兩步:

A = A - 100;

B = B + 100;

在Mongdb中,如果A = A - 100;執行完,將會直接入庫生效,沒有回滾段的概念,所以如果此時B = B + 100;出現了問題,是不能回滾上一步A的操作的。

Mongdb在執行多個更新的時候是沒有原子性的。

 

一個寫入操作更新了多個文檔:

當單個寫入操作修改多個文檔時,每個文檔的修改是原子的,但整個操作不是原子的,而其他操作可能會交錯。 但是,您可以使用$ isolation操作符隔離影響多個文檔的單個寫入操作。

當Mongodb執行影響多個文檔的寫入操作的時候,如果在中間某一個文檔出現了錯誤,那么不會回滾之前的提交。之前的提交已經入庫了。

MongoDB不隔離多文檔寫入操作,具有以下特點:

非時間點讀操作。其中一假設讀取操作在時間t1開始,並開始讀取文檔。寫操作然后在稍后的時間t2向個文檔提交更新。讀操作可能會看到寫操作的更新版本,因此讀取操作沒有時間點的概念。

讀取可能會丟失在讀取操作過程中更新的匹配文檔。

使用$ isolation來保證隔離性:

使用$isolated操作符可以保證單個寫入操作修改多個文檔的時候不被交錯。

$isolated其實是在整個數據庫(Mongodb的手冊對這點說明不清楚,也可能是在集合層面加獨占鎖,但是有一點文檔中是說明的,不論在哪個層面加獨占鎖,都會導致真個數據庫單線程化)加獨占鎖(即使是對於WiredTiger存儲引擎也是),在這期間不能進行其他任何的讀寫操作。所以如果$isolated的操作執行的時間過長,會大大的影響系統的並發性能。

例子:

db.foo.update( 
 { status : "A" , $isolated : 1 }, 
 { $inc : { count : 1 } }, 
 { multi: true } 
) 

注:上面說的影響不是說可以保證多個文檔更新的原子性,$ isolation隔離操作符不為寫入操作提供"all-or-nothing"原子性(原子性的定義是要么全部成功,要么全部失敗,$isolation不能保證出錯回滾)。沒有$isolation運算符,多更新將允許其他操作與此更新交錯。 如果這些交錯操作包含寫入,則更新操作可能會產生意外的結果。 通過指定$ isolated,您可以保證整個多重更新的隔離。

總結如下:

  • $ isolation不保證多個文檔操作的原子性。
  • $ isolation保證多個文檔操作不會被跟其他操作交錯。
  • $ isolation保證此操作在進行到某一個文檔的更新的時候,在不提交或者回滾之前,不會被客戶端看到。也就是說不會導致這個文檔的查詢產生臟讀。(這一段是我的理解 不一定對)

   

$isolated使用的場景很苛刻。

由於單個文檔可以包含多個嵌入文檔,單個文檔的原子性對於許多實際使用情況是足夠的。 對於一系列寫入操作必須在單個事務中操作的情況,您可以在應用程序中實現兩階段提交。

但是,兩階段的提交只能提供類似事務的語義。 使用兩階段提交確保數據一致性,但是在兩階段提交或回滾期間,應用程序可以返回中間數據。

 

 

副本集中使用readConcern:

 

在使用副本集的時候,寫入操作只寫入到master節點,slaver節點從master節點同步數據,所以讀操作可能讀取到沒有同步到其他slaver的數據。

readConcern:讀隔離(

readConcern選項可用於以下操作:

用於副本集和副本集分片的readConcern查詢選項確定從查詢返回哪些數據。

readConcern級別:

"local":默認。該查詢返回實例的最新數據。不保證數據已寫入大多數副本集成員(即可以回滾)。

"majority":該查詢會將實例的最新數據確認為已寫入副本集中的大多數成員。要使用majority級別,您必須使用--enableMajorityReadConcern命令行選項啟動mongod實例(如果使用配置文件,則將replication.enableMajorityReadConcern設置為true)。

"linearizable"(add in version3.4):該查詢返回反映所有成功寫入的數據。

   

這么說如果配置了linearizable 那么針對一個集合的查詢就可以避免臟讀了。因為Mongdb沒有事務,所以也就不存在幻讀和不可重復讀的定義了。不過這個功能是在當前最新的3.4版本才有的。

 

readConcern 解決什么問題?

的初衷在於解決『臟讀』的問題,比如用戶從 MongoDB primary 上讀取了某一條數據,但這條數據並沒有同步到大多數節點,然后 primary 就故障了,重新恢復后 這個primary 節點會將未同步到大多數節點的數據回滾掉,導致用戶讀到了『臟數據』。

當指定 readConcern 級別為 majority 時,能保證用戶讀到的數據『已經寫入到大多數節點』,而這樣的數據肯定不會發生回滾,避免了臟讀的問題()readConcern 能保證讀到的數據『不會發生回滾』,但並不能保證讀到的數據是最新的,這個官網上也有說明:

在使用副本集的時候,無論讀取關注級別如何,節點上的最新數據可能不會反映系統中最新版本的數據。

有用戶誤以為,指定為 majority 時,客戶端會從大多數的節點讀取數據,然后返回最新的數據。

實際上並不是這樣,無論何種級別的 注意事項

  1. 檢索事務開始:

var t = db.transactions.findOne( { state: "initial" , source: "A", destination: "B"} )

  1. Update transaction state to pending:

 

db.transactions.update(

{ _id: t._id, state: "initial" },

{

$set: { state: "pending" },

$currentDate: { lastModified: true }

}

)

WriteResult nnModified1

nMatchednModified0

  1. Apply the transaction to both accounts.

 

db.accounts.update(

{ _id: t.source, pendingTransactions: { $ne: t._id } },

{ $inc: { balance: -t.value }, $push: { pendingTransactions: t._id } }

)

db.accounts.update(

{ _id: t.destination, pendingTransactions: { $ne: t._id } },

{ $inc: { balance: t.value }, $push: { pendingTransactions: t._id } }

)

  1. Update transaction state to applied

db.transactions.update(

{ _id: t._id, state: "pending" },

{

$set: { state: "applied" },

$currentDate: { lastModified: true }

}

)

  1. remove both accounts' list of pending transactions

 

db.accounts.update(

{ _id: t.source, pendingTransactions: t._id },

{ $pull: { pendingTransactions: t._id } }

)

db.accounts.update(

{ _id: t.destination, pendingTransactions: t._id },

{ $pull: { pendingTransactions: t._id } }

)

  1. Update transaction state to done.

 

db.transactions.update(

{ _id: t._id, state: "applied" },

{

$set: { state: "done" },

$currentDate: { lastModified: true }

}

)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM