MongoDB 操作手冊CRUD 事務 兩步提交,mongodbcrud

來源:互聯網
上載者:User

MongoDB 操作手冊CRUD 事務 兩步提交,mongodbcrud
執行兩步提交
概述這部分提供了多記錄更新或者多記錄事務,使用兩步提交來完成多記錄寫入的模板。另外,可以擴充此方法來提供rollback-like功能。
背景MongoDB對於單條記錄的操作是原子性的;但是涉及多條記錄的操作卻不是原子性的。由於記錄可能是相當複雜,並且有內嵌記錄,單記錄原子性操作提供了實際中常用的必要支援。
除了單記錄的原子性操作,還有許多情況需要多記錄操作事務,當執行一個包含一些列操作的事務時,就有以下要求:
原子性:如果一個操作失敗,事務中之前的操作需要復原到之前的狀態
一致性:如果一個重大失誤,比如網路故障,硬體故障,中斷了事務,資料庫必須能夠恢複到之前的狀態
對於需要多記錄操作的事務,可以在應用中實現兩步提交的方法,來提供多記錄更新支援。使用這種方法保證了一致性,並且萬一出現錯誤,事務的執行狀態是可恢複的。然而在這個過程中,記錄處於未定的資料和狀態。
注意:因為MongoDB只有單記錄操作是原子性的,兩步提交只能提供語義上的“類事務”功能。對於應用來說,使其能夠回到在兩步提交中的某個狀態的中間資料或者復原資料。
模板考慮以下情景:

要將資金從賬戶A轉移到賬戶B,在關係型資料庫中,可以在一個事務中從A中減去資金,同時在B中加上。在MongoDB中,可以類比兩步提交來獲得相同結果。

這個例子使用兩個集合
1.accounts,用於儲存賬戶資訊
2.transactions,用於儲存資金轉移事務的資訊
初始化賬戶資訊db.accounts.insert(
[
{ _id: "A", balance: 1000, pendingTransactions: [] },
{ _id: "B", balance: 1000, pendingTransactions: [] }
]
);
初始化轉賬記錄對於每次資金轉移操作,將轉賬資訊添加到transactions集合中,插入的記錄包含以下資訊:
source和destination欄位,引用自ccounts集合中的_id欄位
value欄位,聲明轉移數值
state欄位,表明當前轉移狀態,值可以是initial,pending, applied, done, canceling, 或者 canceled.
lastModified欄位,反應最後修改日期

從A轉賬100到B,初始化transactions記錄:
db.transactions.insert({ _id: 1, source: "A", destination: "B", value: 100, state: "initial", lastModified: new Date() });
使用兩步提交進行轉賬1.從transactions集合中,找到state為initial的記錄。此時transactions集合中只有一條記錄,即剛插入的那條。在包含其他記錄的集合中,除非你聲明了其他查詢條件,否則這個查詢將返回任何state為initial的記錄。
var t = db.transactions.findOne( { state: "initial" } );
在MongoDB的shell中輸入t,查看t的內容,類似於:
{ "_id" : 1, "source" : "A", "destination" : "B", "value" : 100, "state" : "initial", "lastModified":??}
2.更新事務狀態為pending
設定state為pending,lastModified為目前時間
db.transactions.update(
{ _id: t._id, state: "initial" },
{
$set: { state: "pending" },
$currentDate: { lastModified: true }
}
)
在更新操作中,state:'initial'確保沒有其他進程已經更新了這條記錄。如果nMatched和nModified是0,回到第一步,擷取一個新的事務,重新開始這個過程。
3.在兩個賬戶中應用該事務
使用update方法將事務t應用到兩個賬戶中。在更新條件中,包含條件pendingTransactions:{$ne:t._id},以避免重複應用該事務。
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 } }
)
從A帳號減去t.value,給B賬戶加上t.value,同時給每個賬戶的pendingTransactions數組添加事務id
4.更新事務狀態為applied
db.transactions.update(
{ _id: t._id, state: "pending" },
{
$set: { state: "applied" },
$currentDate: { lastModified: true }
}
)
5.更新賬戶pendingTransactions數組
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 } }
)
從兩個賬戶中移除已應用的事務。
6.更新事務狀態為done
db.transactions.update(
{ _id: t._id, state: "applied" },
{
$set: { state: "done" },
$currentDate: { lastModified: true }
}
)
從失敗情境中恢複資料事務最重要的不是以上這個例子提供的原型,而是當事務沒有完全執行成功的時候,從各種失敗情境中恢複資料的可能性。
恢複操作
兩步提交模型允許應用程式重新執行事務操作序列,以保證資料一致性。在程式啟動時,或者定時執行恢複操作,來抓取任何未完成的事務。
恢複到資料一致的狀態的時間取決於應用程式多久需要恢複每個事務。
以下恢複操作使用lastModified日期作為pending狀態的事務是否需要復原的標識符。如果pending或者applied狀態的事務在最近的30分鐘內沒有被更新,說明這些事務需要被恢複。可以使用不同的條件來決定是否需要恢複。

Pending狀態的事務恢複事務狀態在pending之後,applied之前
例:
擷取三十分鐘內未成功的事務記錄
var dateThreshold = new Date();
dateThreshold.setMinutes(dateThreshold.getMinutes() - 30);
var t = db.transactions.findOne( { state: "pending", lastModified: { $lt: dateThreshold } } );
然後回到“3.在兩個賬戶中應用該事務”這一步
Applied狀態的事務例:
擷取三十分鐘內未成功的事務記錄
var dateThreshold = new Date();
dateThreshold.setMinutes(dateThreshold.getMinutes() - 30);
var t = db.transactions.findOne( { state: "applied", lastModified: { $lt: dateThreshold } } );
然後回到“5.更新賬戶pendingTransactions數組”這一步
復原操作有些情況下,可能需要復原,或者撤銷操作,比如,應用程式需要取消事務,或者其中一個賬戶不存在或者被凍結。
Applied狀態的事務在“4.更新事務狀態為applied”這一步之後,不應該再復原事務,而是應該完成當前事務,然後建立一個新的事務來把資料修改回來。
Pending狀態的事務在“2.更新事務狀態為pending”這一步之後,“4.更新事務狀態為applied”這一步之前,可以通過以下步驟復原事務:
1.更新事務狀態為取消中
db.transactions.update(
{ _id: t._id, state: "pending" },
{
$set: { state: "canceling" },
$currentDate: { lastModified: true }
}
)
2.在兩個賬戶中取消操作
如果事務已經應用,需要回退這個事務以取消在兩個賬戶上的操作。在更新的條件中,包含pendingTransactions:t._id,以便在pending transaction已經被應用的時候更新賬戶。
更新目標賬戶,減去事務中給其增加的值,cong pendingTransactions數組中移除事務_id
db.accounts.update(
{ _id: t.destination, pendingTransactions: t._id },
{
$inc: { balance: -t.value },
$pull: { pendingTransactions: t._id }
}
)
如果pending transaction還沒有被應用到這個賬戶中,將不會有記錄匹配查詢條件。
3.更新事務狀態為已取消
db.transactions.update(
{ _id: t._id, state: "canceling" },
{
$set: { state: "cancelled" },
$currentDate: { lastModified: true }
}
)
更新事務狀態為cancelled來標誌事務已取消。
多應用情景由於事務的存在,多個應用可以同時建立和執行操作,而不會產生資料不一致或者衝突。在之前的例子中,更新或者復原記錄,包含state欄位的更新條件防止不同應用重複提交事務
例如,app1和app2同時擷取了一個在initial狀態的事務。app1在app2開始前提交了整個事務。當app2試圖更新事務狀態為pending的時候,包含state:'initial'的更新條件將不會匹配任何記錄,
同時nMatched和nModified將為0.這就表明app2需要回到第一步,重啟一個不同的事務過程。
當多個應用啟動並執行時候,關鍵在有只有一個應用可以及時處理指定的事務。這樣的話,即使在有符合更新條件的記錄,也可以在事務記錄中建立一個標記來標誌應用正在處理這個事務。使用findAndModify()方法來修改事務,並且回退。
t = db.transactions.findAndModify(
{
query: { state: "initial", application: { $exists: false } },
update:
{
$set: { state: "pending", application: "App1" },
$currentDate: { lastModified: true }
},
new: true
}
)
修正後的事務操作確保只有標識符匹配的應用可以提交該事務。
如果app1在事務執行中失敗,可以使用之前講的恢複操作,但是應用程式需要在應用事務之前確保“擁有”該事務。
例如,尋找並恢複pending狀態的job
var dateThreshold = new Date();
dateThreshold.setMinutes(dateThreshold.getMinutes() - 30);
db.transactions.find(
{
application: "App1",
state: "pending",
lastModified: { $lt: dateThreshold }
}
)
在生產環境中使用兩步提交以上的例子是有意寫的很簡單。例如,它假設一個賬戶的復原操作總是可能的,並且賬戶可以儲存負值。
生產環境可能會更加負值,通常來說,賬戶需要當前賬戶值,信用,欠款等多種資訊。
對於所有事務,要確保使用的是write concern許可權等級。
為何MongoDB可以替代MySQL?

MongoDB是一個面向文檔的資料庫,目前由10gen開發並維護,它的功能豐富,齊全,完全可以替代MySQL。在使用MongoDB做產品原型的過程中,我們總結了MonogDB的一些亮點:使用JSON風格文法,易於掌握和理解:MongoDB使用JSON的變種BSON作為內部儲存的格式和文法。針對MongoDB的操作都使用JSON風格文法,用戶端提交或接收的資料都使用JSON形式來展現。相對於SQL來說,更加直觀,容易理解和掌握。Schema-less,支援嵌入子文檔:MongoDB是一個Schema-free的文檔資料庫。一個資料庫可以有多個Collection,每個Collection是Documents的集合。Collection和Document和傳統資料庫的Table和Row並不對等。無需事先定義Collection,隨時可以建立。Collection中可以包含具有不同schema的文檔記錄。 這意味著,你上一條記錄中的文檔有3個屬性,而下一條記錄的文檔可以有10個屬性,屬性的類型既可以是基本的資料類型(如數字、字串、日期等),也可以是數組或者散列,甚至還可以是一個子文檔(embed document)。這樣,可以實現逆正常化(denormalizing)的資料模型,提高查詢的速度。圖1 MongoDB是一個Schema-free的文檔資料庫圖2是一個例子,作品和評論可以設計為一個collection,評論作為子文檔內嵌在art的comments屬性中,評論的回複則作為comment子文檔的子文檔內嵌於replies屬性。按照這種設計模式,只需要按照作品id檢索一次,即可獲得所有相關的資訊了。在MongoDB中,不強調一定對資料進行Normalize ,很多場合都建議De-normalize,開發人員可以扔掉傳統關聯式資料庫各種範式的限制,不需要把所有的實體都映射為一個Collection,只需定義最頂級的class。MongoDB的文檔模型可以讓我們很輕鬆就能將自己的Object映射到collection中實現儲存。圖2 MongoDB支援嵌入子文檔簡單易用的查詢方式:MongoDB中的查詢讓人很舒適,沒有SQL難記的文法,直接使用JSON,相當的直觀。對不同的開發語言,你可以使用它最基本的數組或散列格式進行查詢。配合附加的operator,MongoDB支援範圍查詢,Regex查詢,對子文檔內屬性的查詢,可以取代原來大多數任務的SQL查詢。CRUD更加簡單,支援in-place update:只要定義一個數組,然後傳遞給MongoDB的insert/update方法就可自動插入或更新;對於更新模式,MongoDB支援一個upsert選項,即:“如果記錄存在那麼更新,否則插入”。MongoDB的update方法還支援Modifier,通過Modifier可實現在服務端即時更新,省去用戶端和服務端的通訊。這些modifer可以讓MongoDB具有和Redis、Memcached等KV類似的功能:較之MySQL,MonoDB更加簡單快速。
 

相關文章

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.