MySQL PK MongoDB:多文檔事務(wù)支持,誰更友好?
MongoDB 4.0已經(jīng)發(fā)布GA版本,并且有許多新功能和改進(jìn)。在本文中,我們將重點關(guān)注主要功能,毫無疑問,這是對多文檔ACID事務(wù)的支持。我們將圍繞隔離級別、可重復(fù)讀、幻讀、事務(wù)沖突檢測等主要功能,通過實驗與MySQL一一做對比。
首先會簡述介紹下ACID事務(wù)隔離級別,然后再步入測試主題正文。
原子性(Atomicity):事務(wù)包含的所有操作要么全部成功,要么全部失敗,不存在成功一半的概念。典型例子“西方二元對立思想——非此即彼”:在二元邏輯體系中只存在兩種邏輯值,就是對和錯,或正和負(fù),不存在既對又錯或非正非負(fù)的其他狀態(tài)。
一致性(Consistency):一個事務(wù)執(zhí)行之前和執(zhí)行之后都必須處于一致性狀態(tài)。典型例子:“金龍,你借我5000元,下個月開支給你。”不論金龍用什么方式給我轉(zhuǎn)賬,分幾次轉(zhuǎn),借錢結(jié)束后我銀行卡里的余額增加5000元,金龍卡里少了5000元,不能突然蹦出來1萬來。
隔離性(Isolation):數(shù)據(jù)庫采用鎖機(jī)制來實現(xiàn)事務(wù)的隔離性,當(dāng)多個事務(wù)同時更新數(shù)據(jù)庫中相同的數(shù)據(jù)時,只允許持有鎖的事務(wù)能更新該數(shù)據(jù),其他事務(wù)必須等待,直到前一個事務(wù)釋放了鎖,其他事務(wù)才有機(jī)會更新該數(shù)據(jù)。典型例子:“你去醫(yī)院看病,要先到護(hù)士那里分診排號,如果多個患者加塞兒同時進(jìn)來,大夫就發(fā)飆了,出去排隊,只能一個一個看”。
持久性(Durability):事務(wù)成功提交后,它對數(shù)據(jù)庫所做的修改就***保存下來,即使數(shù)據(jù)庫崩潰,數(shù)據(jù)還能恢復(fù)到事務(wù)成功提交后的狀態(tài)。典型例子:“你去ATM機(jī)取錢,結(jié)果ATM機(jī)故障了,你取出來了錢,卡里的余額不能沒減。”
一、局限性與限制條件
1、多文檔事務(wù)僅適用于副本集。
注:如果是單機(jī),需切到副本集模式。
2、僅適用于WiredTiger存儲引擎。
3、如果你的架構(gòu)是分片Sharding模式,事務(wù)是不支持的。分布式事務(wù)計劃在4.2版本里支持。
4、事務(wù)只支持CRUD操作,DDL、DCL操作不支持。
注:CRUD就是MySQL的DML,意思一樣叫法不同而已。
5、事務(wù)無法在config、admin和local系統(tǒng)數(shù)據(jù)庫中讀取或?qū)懭搿?nbsp;
6、事務(wù)無法在system.*(系統(tǒng)集合)里寫入。
7、不能有大事務(wù)寫入,寫入集不能超過16MB(類似MariaDB Galera Cluster寫入集wsrep_max_ws_size限制),否則客戶端直接報錯。
注:如果有大事務(wù),應(yīng)該考慮將這些大事務(wù)拆分成若干塊較小的事務(wù)。例如將大于2018年的狀態(tài)值更改為1,應(yīng)考慮循環(huán)1萬條一批量更新,這一點跟MySQL玩法一樣。
二、我們***個事務(wù)
在開始事務(wù)之前,必須創(chuàng)建會話。事務(wù)不能在會話外運(yùn)行。
- var session1 = db.getMongo().startSession()
- var session2 = db.getMongo().startSession()
Mongo Shell里引入了三個用于創(chuàng)建,提交和終止事務(wù)的新命令:
- session.startTransaction()
在當(dāng)前會話中啟動事務(wù)
- session.commitTransaction()
持久保存事務(wù)中的操作變更
- session.abortTransaction()
終止事務(wù)操作所做的變更
1、在test庫創(chuàng)建t1表,并且插入4條數(shù)據(jù)。
演示一
演示二
空閑事務(wù)受transactionLifetimeLimitSeconds參數(shù)影響,默認(rèn)60秒。
可通過以下命令查看:
- db.adminCommand( { getParameter: 1, transactionLifetimeLimitSeconds: 1 } )
如果你想在線變更,可以通過下面的命令設(shè)置:
- db.adminCommand( { setParameter: 1, transactionLifetimeLimitSeconds: 30 } )
也可以寫死在/etc/mongod.cnf配置文件里***生效,格式如下:
- setParameter = transactionLifetimeLimitSeconds=30
注:空閑事務(wù)是指當(dāng)一個事務(wù)長時間未提交,那么這個連接就不能關(guān)閉,內(nèi)存就不釋放,并發(fā)一大,導(dǎo)致DB連接數(shù)增多,就會對性能產(chǎn)生影響。默認(rèn)是60秒,你可以根據(jù)自己的情況設(shè)定閾值。超過這個閾值,服務(wù)端自動殺死未提交的空閑事務(wù)。
三、事務(wù)隔離性演示
演示一:事務(wù)沖突檢測
當(dāng)兩個(或多個)并發(fā)事務(wù)修改相同的文檔時,會發(fā)生沖突。即使在尚未提交事務(wù)時,MongoDB也可以立即檢測到?jīng)_突。
這里和MySQL有些區(qū)別,MySQL可以通過參數(shù)innodb_lock_wait_timeout設(shè)置檢測到事務(wù)沖突后,自動終止回滾的時間,而MongoDB沒有提供該參數(shù)。
當(dāng)在執(zhí)行創(chuàng)建索引時,未加{background:1})后臺創(chuàng)建。
此時新事務(wù)將無法獲取所需的鎖,并且在等待參數(shù)maxTransactionLockRequestTimeoutMillis后事務(wù)終止回滾,默認(rèn)值是5毫秒。
如果你想在線調(diào)整事務(wù)等待獲取鎖的時間,可以通過下面的命令設(shè)置:
- db.adminCommand( { setParameter: 1, maxTransactionLockRequestTimeoutMillis: 15 } )
也可以寫死在/etc/mongod.cnf配置文件里***生效,格式如下:
- setParameter = maxTransactionLockRequestTimeoutMillis=15
演示二:可重復(fù)讀
Repeatable Read (可重復(fù)讀)可避免臟讀、不可重復(fù)讀的發(fā)生。
不可重復(fù)讀側(cè)重點在于更新修改的數(shù)據(jù),即在同一個事務(wù)里,兩次查詢的數(shù)據(jù)結(jié)果不一致。與臟讀的區(qū)別是:臟讀是一個事務(wù)讀取了另一個事務(wù)未提交的臟數(shù)據(jù)。
演示三:幻讀
在MySQL默認(rèn)隔離級Repeatable Read下,剛才的操作,在會話二未提交的事務(wù)里,會莫名其妙地看到第5條數(shù)據(jù),這種現(xiàn)象稱為幻讀。
幻讀和不可重復(fù)讀很像,但幻讀側(cè)重點在于新增和刪除,而不可重復(fù)讀側(cè)重點在于更改,共同之處都是一個事務(wù)中兩次查詢得到的數(shù)據(jù)結(jié)果不一致。
由此,從測試結(jié)果得出的結(jié)論是:
MongoDB采用的默認(rèn)隔離級別是Snapshot一致性快照(特別是設(shè)置了readConcern=majority情況下,要讀某行數(shù)據(jù)的歷史版本時,依賴該隔離級別。)
Snapshot介于Repeatable Read與Serializable之間,既避免了臟讀、不可重復(fù)讀、幻讀,又不會因Serializable串行化降低并發(fā)性能。
參考文獻(xiàn)
- https://docs.mongodb.com/manual/core/transactions/
- https://www.percona.com/blog/2018/12/04/mongodb-4-0-using-acid-multi-document-transactions/