一般所有關(guān)系型數(shù)據(jù)庫內(nèi)部都有自己的事務(wù)機(jī)制,進(jìn)程是如何保證每個(gè)查詢在自己的事務(wù)內(nèi)執(zhí)行的,通過這篇文章來簡單介紹一下。
我們可以理解為數(shù)據(jù)庫是由多種相互交互的組件構(gòu)成的,數(shù)據(jù)庫一般可以用如下圖形來理解:
事務(wù)管理器就是今天要介紹的其中一個(gè)組件:Transaction manager
在講事務(wù)之前,我們需要理解ACID事務(wù)的概念。
一個(gè)ACID事務(wù)是一個(gè)工作單元,它要保證4個(gè)屬性:
- 原子性(Atomicity): 事務(wù)『要么全部完成,要么全部取消』,即使它持續(xù)運(yùn)行10個(gè)小時(shí)。如果事務(wù)崩潰,狀態(tài)回到事務(wù)之前(事務(wù)回滾)。
- 隔離性(Isolation): 如果2個(gè)事務(wù) A 和 B 同時(shí)運(yùn)行,事務(wù) A 和 B 最終的結(jié)果是相同的,不管 A 是結(jié)束于 B 之前/之后/運(yùn)行期間。
- 持久性(Durability): 一旦事務(wù)提交(也就是成功執(zhí)行),不管發(fā)生什么(崩潰或者出錯(cuò)),數(shù)據(jù)要保存在數(shù)據(jù)庫中。
- 一致性(Consistency): 只有合法的數(shù)據(jù)(依照關(guān)系約束和函數(shù)約束)能寫入數(shù)據(jù)庫,一致性與原子性和隔離性有關(guān)。
現(xiàn)代數(shù)據(jù)庫不會使用純粹的隔離作為默認(rèn)模式,因?yàn)樗鼤砭薮蟮男阅芟?。SQL一般定義4個(gè)隔離級別:
- 串行化(Serializable,SQLite默認(rèn)模式):最高級別的隔離。兩個(gè)同時(shí)發(fā)生的事務(wù)100%隔離,每個(gè)事務(wù)有自己的『世界』。
- 可重復(fù)讀(Repeatable read,MySQL默認(rèn)模式):每個(gè)事務(wù)有自己的『世界』,除了一種情況。如果一個(gè)事務(wù)成功執(zhí)行并且添加了新數(shù)據(jù),這些數(shù)據(jù)對其他正在執(zhí)行的事務(wù)是可見的。但是如果事務(wù)成功修改了一條數(shù)據(jù),修改結(jié)果對正在運(yùn)行的事務(wù)不可見。所以,事務(wù)之間只是在新數(shù)據(jù)方面突破了隔離,對已存在的數(shù)據(jù)仍舊隔離。 舉個(gè)例子,如果事務(wù)A運(yùn)行”SELECT count(1) from TABLE_X” ,然后事務(wù)B在 TABLE_X 加入一條新數(shù)據(jù)并提交,當(dāng)事務(wù)A再運(yùn)行一次 count(1)結(jié)果不會是一樣的。 這叫幻讀(phantom read)。
- 讀取已提交(Read committed,Oracle、PostgreSQL、SQL Server默認(rèn)模式):可重復(fù)讀+新的隔離突破。如果事務(wù)A讀取了數(shù)據(jù)D,然后數(shù)據(jù)D被事務(wù)B修改(或刪除)并提交,事務(wù)A再次讀取數(shù)據(jù)D時(shí)數(shù)據(jù)的變化(或刪除)是可見的。 這也叫不可重復(fù)讀(non-repeatable read)。
- 讀取未提交(Read uncommitted):最低級別的隔離,是讀取已提交+新的隔離突破。如果事務(wù)A讀取了數(shù)據(jù)D,然后數(shù)據(jù)D被事務(wù)B修改(但并未提交,事務(wù)B仍在運(yùn)行中),事務(wù)A再次讀取數(shù)據(jù)D時(shí),數(shù)據(jù)修改是可見的。如果事務(wù)B回滾,那么事務(wù)A第二次讀取的數(shù)據(jù)D是無意義的,因?yàn)槟鞘鞘聞?wù)B所做的從未發(fā)生的修改(已經(jīng)回滾了嘛)。 這也叫臟讀(dirty read)。
默認(rèn)的隔離級別可以由用戶/開發(fā)者在建立連接時(shí)指定,一般mysql默認(rèn)的隔離級別是 可重復(fù)讀?,當(dāng)然,隔離級別越高的話,對應(yīng)的效率是越低的。
并發(fā)控制
確保隔離性、一致性和原子性的真正問題是對相同數(shù)據(jù)的寫操作(增、更、刪):
- 如果所有事務(wù)只是讀取數(shù)據(jù),它們可以同時(shí)工作,不會更改另一個(gè)事務(wù)的行為。
- 如果多個(gè)事務(wù)同時(shí)操作同一個(gè)數(shù)據(jù),可能出現(xiàn)事務(wù)A的更改被事務(wù)B或者C所覆蓋等等。
因此在高并發(fā)下要對可能出現(xiàn)的一些問題做?并發(fā)控制。
最簡單的解決辦法是依次執(zhí)行每個(gè)事務(wù)(即順序執(zhí)行),但這樣就完全沒有伸縮性了,在一個(gè)多處理器/多核服務(wù)器上只有一個(gè)核心在工作,效率很低。
為了解決這個(gè)問題,多數(shù)數(shù)據(jù)庫使用鎖和/或數(shù)據(jù)版本控制。
悲觀鎖
原理是:
如果一個(gè)事務(wù)需要一條數(shù)據(jù),它就把數(shù)據(jù)鎖住,如果另一個(gè)事務(wù)也需要這條數(shù)據(jù),它就必須要等第一個(gè)事務(wù)釋放這條數(shù)據(jù) 這個(gè)鎖叫排他鎖。
但是對一個(gè)僅僅讀取數(shù)據(jù)的事務(wù)使用排他鎖非常昂貴,因?yàn)?strong>這會迫使其它只需要讀取相同數(shù)據(jù)的事務(wù)等待。因此就有了另一種鎖,共享鎖。
共享鎖是這樣的:
如果一個(gè)事務(wù)只需要讀取數(shù)據(jù)A,它會給數(shù)據(jù)A加上『共享鎖』并讀取,如果第二個(gè)事務(wù)也需要僅僅讀取數(shù)據(jù)A,它會給數(shù)據(jù)A加上『共享鎖』并讀取,如果第三個(gè)事務(wù)需要修改數(shù)據(jù)A,它會給數(shù)據(jù)A加上『排他鎖』,但是必須等待另外兩個(gè)事務(wù)釋放它們的共享鎖。
同樣的,如果一塊數(shù)據(jù)被加上排他鎖,一個(gè)只需要讀取該數(shù)據(jù)的事務(wù)必須等待排他鎖釋放才能給該數(shù)據(jù)加上共享鎖。
鎖管理器是添加和釋放鎖的進(jìn)程,在內(nèi)部用一個(gè)哈希表保存鎖信息(關(guān)鍵字是被鎖的數(shù)據(jù)),并且了解每一塊數(shù)據(jù)是:
- 被哪個(gè)事務(wù)加的鎖
- 哪個(gè)事務(wù)在等待數(shù)據(jù)解鎖
死鎖
使用鎖會導(dǎo)致一種情況,2個(gè)事務(wù)永遠(yuǎn)在等待一塊數(shù)據(jù)
如上圖所示:
- 事務(wù)A 給 數(shù)據(jù)1 加上排他鎖并且等待獲取數(shù)據(jù)2
- 事務(wù)B 給 數(shù)據(jù)2 加上排他鎖并且等待獲取數(shù)據(jù)1
這叫死鎖。
在死鎖發(fā)生時(shí),鎖管理器要選擇取消(回滾)一個(gè)事務(wù),以便消除死鎖,這也是一個(gè)比較耗時(shí)的過程。
哈希表可以看作是個(gè)圖表(見上文圖),圖中出現(xiàn)循環(huán)就說明有死鎖。由于檢查循環(huán)是昂貴的(所有鎖組成的圖表是很龐大的),經(jīng)常會通過簡單的途徑解決:使用超時(shí)設(shè)定。如果一個(gè)鎖在超時(shí)時(shí)間內(nèi)沒有加上,那事務(wù)就進(jìn)入死鎖狀態(tài)。
事務(wù)開始時(shí)獲取鎖,結(jié)束時(shí)釋放鎖,這是可以實(shí)現(xiàn)純粹的隔離,但是這種方法會等待所有的鎖,大量的時(shí)間被浪費(fèi)了。
更快的方法是兩段鎖協(xié)議(Two-Phase Locking Protocol,由 DB2 和 SQL Server使用),在這里,事務(wù)分為兩個(gè)階段:
- 成長階段:事務(wù)可以獲得鎖,但不能釋放鎖。
- 收縮階段:事務(wù)可以釋放鎖(對于已經(jīng)處理完而且不會再次處理的數(shù)據(jù)),但不能獲得新鎖。
?
這兩條簡單規(guī)則背后的原理是:
- 釋放不再使用的鎖,來降低其它事務(wù)的等待時(shí)間
- 防止發(fā)生這類情況:事務(wù)最初獲得的數(shù)據(jù),在事務(wù)開始后被修改,當(dāng)事務(wù)重新讀取該數(shù)據(jù)時(shí)發(fā)生不一致。
這個(gè)規(guī)則可以很好地工作,但有個(gè)例外:如果修改了一條數(shù)據(jù)、釋放了關(guān)聯(lián)的鎖后,事務(wù)被取消(回滾),而另一個(gè)事務(wù)讀到了修改后的值,但最后這個(gè)值卻被回滾。為了避免這個(gè)問題,所有獨(dú)占鎖必須在事務(wù)結(jié)束時(shí)釋放。
數(shù)據(jù)版本控制是解決這個(gè)問題的另一個(gè)方法。
版本控制是這樣的:
- 每個(gè)事務(wù)可以在相同時(shí)刻修改相同的數(shù)據(jù)
- 每個(gè)事務(wù)有自己的數(shù)據(jù)拷貝(或者叫版本)
- 如果2個(gè)事務(wù)修改相同的數(shù)據(jù),只接受一個(gè)修改,另一個(gè)將被拒絕,相關(guān)的事務(wù)回滾(或重新運(yùn)行)
這將提高性能,因?yàn)椋?mark hidden color="red">文章來源:http://www.zghlxwxcb.cn/news/detail-420949.html
- 讀事務(wù)不會阻塞寫事務(wù)
- 寫事務(wù)不會阻塞讀
- 沒有『臃腫緩慢』的鎖管理器帶來的額外開銷
一些數(shù)據(jù)庫,比如DB2(直到版本 9.7)和 SQL Server(不含快照隔離)僅使用鎖機(jī)制。其他的像PostgreSQL, MySQL 和 Oracle 使用鎖和鼠標(biāo)版本控制混合機(jī)制。文章來源地址http://www.zghlxwxcb.cn/news/detail-420949.html
到了這里,關(guān)于數(shù)據(jù)庫底層運(yùn)行原理之——事務(wù)管理器的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!