溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

MySQL中什么是事務(wù)與鎖

發(fā)布時間:2021-08-06 11:15:05 來源:億速云 閱讀:117 作者:Leah 欄目:數(shù)據(jù)庫

這篇文章給大家介紹MySQL中什么是事務(wù)與鎖,內(nèi)容非常詳細,感興趣的小伙伴們可以參考借鑒,希望對大家能有所幫助。

  MySQL 中事務(wù)的實現(xiàn)

  在關(guān)系型數(shù)據(jù)庫中,事務(wù)的重要性不言而喻,只要對數(shù)據(jù)庫稍有了解的人都知道事務(wù)具有 ACID 四個基本屬性,而我們不知道的可能就是數(shù)據(jù)庫是如何實現(xiàn)這四個屬性的;在這篇文章中,我們將對事務(wù)的實現(xiàn)進行分析,嘗試理解數(shù)據(jù)庫是如何實現(xiàn)事務(wù)的,當然我們也會在文章中簡單對 MySQL 中對 ACID 的實現(xiàn)進行簡單的介紹。

  事務(wù)其實就是并發(fā)控制的基本單位;相信我們都知道,事務(wù)是一個序列操作,其中的操作要么都執(zhí)行,要么都不執(zhí)行,它是一個不可分割的工作單位;數(shù)據(jù)庫事務(wù)的 ACID 四大特性是事務(wù)的基礎(chǔ),了解了 ACID 是如何實現(xiàn)的,我們也就清除了事務(wù)的實現(xiàn),接下來我們將依次介紹數(shù)據(jù)庫是如何實現(xiàn)這四個特性的。

  原子性

  在學(xué)習(xí)事務(wù)時,經(jīng)常有人會告訴你,事務(wù)就是一系列的操作,要么全部都執(zhí)行,要都不執(zhí)行,這其實就是對事務(wù)原子性的刻畫;雖然事務(wù)具有原子性,但是原子性并不是只與事務(wù)有關(guān)系,它的身影在很多地方都會出現(xiàn)。

  由于操作并不具有原子性,并且可以再分為多個操作,當這些操作出現(xiàn)錯誤或拋出異常時,整個操作就可能不會繼續(xù)執(zhí)行下去,而已經(jīng)進行的操作造成的副作用就可能造成數(shù)據(jù)更新的丟失或者錯誤。

  事務(wù)其實和一個操作沒有什么太大的區(qū)別,它是一系列的數(shù)據(jù)庫操作(可以理解為 SQL)的集合,如果事務(wù)不具備原子性,那么就沒辦法保證同一個事務(wù)中的所有操作都被執(zhí)行或者未被執(zhí)行了,整個數(shù)據(jù)庫系統(tǒng)就既不可用也不可信。

  回滾日志

  想要保證事務(wù)的原子性,就需要在異常發(fā)生時,對已經(jīng)執(zhí)行的操作進行回滾,而在 MySQL 中,恢復(fù)機制是通過回滾日志(undo log)實現(xiàn)的,所有事務(wù)進行的修改都會先記錄到這個回滾日志中,然后在對數(shù)據(jù)庫中的對應(yīng)行進行寫入。

  這個過程其實非常好理解,為了能夠在發(fā)生錯誤時撤銷之前的全部操作,肯定是需要將之前的操作都記錄下來的,這樣在發(fā)生錯誤時才可以回滾。

  回滾日志除了能夠在發(fā)生錯誤或者用戶執(zhí)行 ROLLBACK 時提供回滾相關(guān)的信息,它還能夠在整個系統(tǒng)發(fā)生崩潰、數(shù)據(jù)庫進程直接被殺死后,當用戶再次啟動數(shù)據(jù)庫進程時,還能夠立刻通過查詢回滾日志將之前未完成的事務(wù)進行回滾,這也就需要回滾日志必須先于數(shù)據(jù)持久化到磁盤上,是我們需要先寫日志后寫數(shù)據(jù)庫的主要原因。

  回滾日志并不能將數(shù)據(jù)庫物理地恢復(fù)到執(zhí)行語句或者事務(wù)之前的樣子;它是邏輯日志,當回滾日志被使用時,它只會按照日志邏輯地將數(shù)據(jù)庫中的修改撤銷掉看,可以理解為,我們在事務(wù)中使用的每一條 INSERT 都對應(yīng)了一條 DELETE,每一條 UPDATE 也都對應(yīng)一條相反的 UPDATE 語句。

  在這里,我們并不會介紹回滾日志的格式以及它是如何被管理的,本文重點關(guān)注在它到底是一個什么樣的東西,究竟解決了、如何解決了什么樣的問題,如果想要了解具體實現(xiàn)細節(jié)的讀者,相信網(wǎng)絡(luò)上關(guān)于回滾日志的文章一定不少。

  事務(wù)的狀態(tài)

  因為事務(wù)具有原子性,所以從遠處看的話,事務(wù)就是密不可分的一個整體,事務(wù)的狀態(tài)也只有三種:Active、Commited 和 Failed,事務(wù)要不就在執(zhí)行中,要不然就是成功或者失敗的狀態(tài):

  但是如果放大來看,我們會發(fā)現(xiàn)事務(wù)不再是原子的,其中包括了很多中間狀態(tài),比如部分提交,事務(wù)的狀態(tài)圖也變得越來越復(fù)雜。

  事務(wù)的狀態(tài)圖以及狀態(tài)的描述取自 Database System Concepts 一書中第 14 章的內(nèi)容。

  Active:事務(wù)的初始狀態(tài),表示事務(wù)正在執(zhí)行;

  Partially Commited:在最后一條語句執(zhí)行之后;

  Failed:發(fā)現(xiàn)事務(wù)無法正常執(zhí)行之后;

  Aborted:事務(wù)被回滾并且數(shù)據(jù)庫恢復(fù)到了事務(wù)進行之前的狀態(tài)之后;

  Commited:成功執(zhí)行整個事務(wù);

  雖然在發(fā)生錯誤時,整個數(shù)據(jù)庫的狀態(tài)可以恢復(fù),但是如果我們在事務(wù)中執(zhí)行了諸如:向標準輸出打印日志、向外界發(fā)出郵件、沒有通過數(shù)據(jù)庫修改了磁盤上的內(nèi)容甚至在事務(wù)執(zhí)行期間發(fā)生了轉(zhuǎn)賬匯款,那么這些操作作為可見的外部輸出都是沒有辦法回滾的;這些問題都是由應(yīng)用開發(fā)者解決和負責(zé)的,在絕大多數(shù)情況下,我們都需要在整個事務(wù)提交后,再觸發(fā)類似的無法回滾的操作

  以訂票為例,哪怕我們在整個事務(wù)結(jié)束之后,才向第三方發(fā)起請求,由于向第三方請求并獲取結(jié)果是一個需要較長事件的操作,如果在事務(wù)剛剛提交時,數(shù)據(jù)庫或者服務(wù)器發(fā)生了崩潰,那么我們就非常有可能丟失發(fā)起請求這一過程,這就造成了非常嚴重的問題;而這一點就不是數(shù)據(jù)庫所能保證的,開發(fā)者需要在適當?shù)臅r候查看請求是否被發(fā)起、結(jié)果是成功還是失敗。

  并行事務(wù)的原子性

  到目前為止,所有的事務(wù)都只是串行執(zhí)行的,一直都沒有考慮過并行執(zhí)行的問題;然而在實際工作中,并行執(zhí)行的事務(wù)才是常態(tài),然而并行任務(wù)下,卻可能出現(xiàn)非常復(fù)雜的問題:

  當 Transaction1 在執(zhí)行的過程中對 id = 1 的用戶進行了讀寫,但是沒有將修改的內(nèi)容進行提交或者回滾,在這時 Transaction2 對同樣的數(shù)據(jù)進行了讀操作并提交了事務(wù);也就是說 Transaction2 是依賴于 Transaction1 的,當 Transaction1 由于一些錯誤需要回滾時,因為要保證事務(wù)的原子性,需要對 Transaction2 進行回滾,但是由于我們已經(jīng)提交了 Transaction2,所以我們已經(jīng)沒有辦法進行回滾操作,在這種問題下我們就發(fā)生了問題,Database System Concepts 一書中將這種現(xiàn)象稱為不可恢復(fù)安排(Nonrecoverable Schedule),那什么情況下是可以恢復(fù)的呢?

  A recoverable schedule is one where, for each pair of transactions Ti and Tj such that Tj reads a data item previously written by Ti , the commit operation of Ti appears before the commit operation of Tj .

  簡單理解一下,如果 Transaction2 依賴于事務(wù) Transaction1,那么事務(wù) Transaction1 必須在 Transaction2 提交之前完成提交的操作:

  然而這樣還不算完,當事務(wù)的數(shù)量逐漸增多時,整個恢復(fù)流程也會變得越來越復(fù)雜,如果我們想要從事務(wù)發(fā)生的錯誤中恢復(fù),也不是一件那么容易的事情。

  在上圖所示的一次事件中,Transaction2 依賴于 Transaction1,而 Transaction3 又依賴于 Transaction1,當 Transaction1 由于執(zhí)行出現(xiàn)問題發(fā)生回滾時,為了保證事務(wù)的原子性,就會將 Transaction2 和 Transaction3 中的工作全部回滾,這種情況也叫做級聯(lián)回滾(Cascading Rollback),級聯(lián)回滾的發(fā)生會導(dǎo)致大量的工作需要撤回,是我們難以接受的,不過如果想要達到絕對的原子性,這件事情又是不得不去處理的,我們會在文章的后面具體介紹如何處理并行事務(wù)的原子性。

  持久性

  既然是數(shù)據(jù)庫,那么一定對數(shù)據(jù)的持久存儲有著非常強烈的需求,如果數(shù)據(jù)被寫入到數(shù)據(jù)庫中,那么數(shù)據(jù)一定能夠被安全存儲在磁盤上;而事務(wù)的持久性就體現(xiàn)在,一旦事務(wù)被提交,那么數(shù)據(jù)一定會被寫入到數(shù)據(jù)庫中并持久存儲起來。

  當事務(wù)已經(jīng)被提交之后,就無法再次回滾了,唯一能夠撤回已經(jīng)提交的事務(wù)的方式就是創(chuàng)建一個相反的事務(wù)對原操作進行『補償』,這也是事務(wù)持久性的體現(xiàn)之一。

  重做日志

  與原子性一樣,事務(wù)的持久性也是通過日志來實現(xiàn)的,MySQL 使用重做日志(redo log)實現(xiàn)事務(wù)的持久性,重做日志由兩部分組成,一是內(nèi)存中的重做日志緩沖區(qū),因為重做日志緩沖區(qū)在內(nèi)存中,所以它是易失的,另一個就是在磁盤上的重做日志文件,它是持久的

  當我們在一個事務(wù)中嘗試對數(shù)據(jù)進行修改時,它會先將數(shù)據(jù)從磁盤讀入內(nèi)存,并更新內(nèi)存中緩存的數(shù)據(jù),然后生成一條重做日志并寫入重做日志緩存,當事務(wù)真正提交時,MySQL 會將重做日志緩存中的內(nèi)容刷新到重做日志文件,再將內(nèi)存中的數(shù)據(jù)更新到磁盤上,圖中的第 4、5 步就是在事務(wù)提交時執(zhí)行的。

  在 InnoDB 中,重做日志都是以 512 字節(jié)的塊的形式進行存儲的,同時因為塊的大小與磁盤扇區(qū)大小相同,所以重做日志的寫入可以保證原子性,不會由于機器斷電導(dǎo)致重做日志僅寫入一半并留下臟數(shù)據(jù)。

  除了所有對數(shù)據(jù)庫的修改會產(chǎn)生重做日志,因為回滾日志也是需要持久存儲的,它們也會創(chuàng)建對應(yīng)的重做日志,在發(fā)生錯誤后,數(shù)據(jù)庫重啟時會從重做日志中找出未被更新到數(shù)據(jù)庫磁盤中的日志重新執(zhí)行以滿足事務(wù)的持久性。

  回滾日志和重做日志

  到現(xiàn)在為止我們了解了 MySQL 中的兩種日志,回滾日志(undo log)和重做日志(redo log);在數(shù)據(jù)庫系統(tǒng)中,事務(wù)的原子性和持久性是由事務(wù)日志(transaction log)保證的,在實現(xiàn)時也就是上面提到的兩種日志,前者用于對事務(wù)的影響進行撤銷,后者在錯誤處理時對已經(jīng)提交的事務(wù)進行重做,它們能保證兩點:

  發(fā)生錯誤或者需要回滾的事務(wù)能夠成功回滾(原子性);

  在事務(wù)提交后,數(shù)據(jù)沒來得及寫會磁盤就宕機時,在下次重新啟動后能夠成功恢復(fù)數(shù)據(jù)(持久性);

  在數(shù)據(jù)庫中,這兩種日志經(jīng)常都是一起工作的,我們可以將它們整體看做一條事務(wù)日志,其中包含了事務(wù)的 ID、修改的行元素以及修改前后的值。

  一條事務(wù)日志同時包含了修改前后的值,能夠非常簡單的進行回滾和重做兩種操作,在這里我們也不會對重做和回滾日志展開進行介紹,可能會在之后的文章談一談數(shù)據(jù)庫系統(tǒng)的恢復(fù)機制時提到兩種日志的使用。

  隔離性

  其實作者在之前的文章 『淺入淺出』MySQL 和 InnoDB 就已經(jīng)介紹過數(shù)據(jù)庫事務(wù)的隔離性,不過為了保證文章的獨立性和完整性,我們還會對事務(wù)的隔離性進行介紹,介紹的內(nèi)容可能稍微有所不同。

  事務(wù)的隔離性是數(shù)據(jù)庫處理數(shù)據(jù)的幾大基礎(chǔ)之一,如果沒有數(shù)據(jù)庫的事務(wù)之間沒有隔離性,就會發(fā)生在 并行事務(wù)的原子性 一節(jié)中提到的級聯(lián)回滾等問題,造成性能上的巨大損失。如果所有的事務(wù)的執(zhí)行順序都是線性的,那么對于事務(wù)的管理容易得多,但是允許事務(wù)的并行執(zhí)行卻能能夠提升吞吐量和資源利用率,并且可以減少每個事務(wù)的等待時間。

  當多個事務(wù)同時并發(fā)執(zhí)行時,事務(wù)的隔離性可能就會被違反,雖然單個事務(wù)的執(zhí)行可能沒有任何錯誤,但是從總體來看就會造成數(shù)據(jù)庫的一致性出現(xiàn)問題,而串行雖然能夠允許開發(fā)者忽略并行造成的影響,能夠很好地維護數(shù)據(jù)庫的一致性,但是卻會影響事務(wù)執(zhí)行的性能。

  事務(wù)的隔離級別

  所以說數(shù)據(jù)庫的隔離性和一致性其實是一個需要開發(fā)者去權(quán)衡的問題,為數(shù)據(jù)庫提供什么樣的隔離性層級也就決定了數(shù)據(jù)庫的性能以及可以達到什么樣的一致性;在 SQL 標準中定義了四種數(shù)據(jù)庫的事務(wù)的隔離級別:READ UNCOMMITED、READ COMMITED、REPEATABLE READ 和 SERIALIZABLE;每個事務(wù)的隔離級別其實都比上一級多解決了一個問題:

  RAED UNCOMMITED:使用查詢語句不會加鎖,可能會讀到未提交的行(Dirty Read);

  READ COMMITED:只對記錄加記錄鎖,而不會在記錄之間加間隙鎖,所以允許新的記錄插入到被鎖定記錄的附近,所以再多次使用查詢語句時,可能得到不同的結(jié)果(Non-Repeatable Read);

  REPEATABLE READ:多次讀取同一范圍的數(shù)據(jù)會返回第一次查詢的快照,不會返回不同的數(shù)據(jù)行,但是可能發(fā)生幻讀(Phantom Read);

  SERIALIZABLE:InnoDB 隱式地將全部的查詢語句加上共享鎖,解決了幻讀的問題;

  以上的所有的事務(wù)隔離級別都不允許臟寫入(Dirty Write),也就是當前事務(wù)更新了另一個事務(wù)已經(jīng)更新但是還未提交的數(shù)據(jù),大部分的數(shù)據(jù)庫中都使用了 READ COMMITED 作為默認的事務(wù)隔離級別,但是 MySQL 使用了 REPEATABLE READ 作為默認配置;從 RAED UNCOMMITED 到 SERIALIZABLE,隨著事務(wù)隔離級別變得越來越嚴格,數(shù)據(jù)庫對于并發(fā)執(zhí)行事務(wù)的性能也逐漸下降。

  對于數(shù)據(jù)庫的使用者,從理論上說,并不需要知道事務(wù)的隔離級別是如何實現(xiàn)的,我們只需要知道這個隔離級別解決了什么樣的問題,但是不同數(shù)據(jù)庫對于不同隔離級別的是實現(xiàn)細節(jié)在很多時候都會讓我們遇到意料之外的坑。

  如果讀者不了解臟讀、不可重復(fù)讀和幻讀究竟是什么,可以閱讀之前的文章 『淺入淺出』MySQL 和 InnoDB,在這里我們僅放一張圖來展示各個隔離層級對這幾個問題的解決情況。

  隔離級別的實現(xiàn)

  數(shù)據(jù)庫對于隔離級別的實現(xiàn)就是使用并發(fā)控制機制對在同一時間執(zhí)行的事務(wù)進行控制,限制不同的事務(wù)對于同一資源的訪問和更新,而最重要也最常見的并發(fā)控制機制,在這里我們將簡單介紹三種最重要的并發(fā)控制器機制的工作原理。

  

  鎖是一種最為常見的并發(fā)控制機制,在一個事務(wù)中,我們并不會將整個數(shù)據(jù)庫都加鎖,而是只會鎖住那些需要訪問的數(shù)據(jù)項, MySQL 和常見數(shù)據(jù)庫中的鎖都分為兩種,共享鎖(Shared)和互斥鎖(Exclusive),前者也叫讀鎖,后者叫寫鎖。

  讀鎖保證了讀操作可以并發(fā)執(zhí)行,相互不會影響,而寫鎖保證了在更新數(shù)據(jù)庫數(shù)據(jù)時不會有其他的事務(wù)訪問或者更改同一條記錄造成不可預(yù)知的問題。

  時間戳

  除了鎖,另一種實現(xiàn)事務(wù)的隔離性的方式就是通過時間戳,使用這種方式實現(xiàn)事務(wù)的數(shù)據(jù)庫,例如 PostgreSQL 會為每一條記錄保留兩個字段;讀時間戳中報錯了所有訪問該記錄的事務(wù)中的最大時間戳,而記錄行的寫時間戳中保存了將記錄改到當前值的事務(wù)的時間戳。

  使用時間戳實現(xiàn)事務(wù)的隔離性時,往往都會使用樂觀鎖,先對數(shù)據(jù)進行修改,在寫回時再去判斷當前值,也就是時間戳是否改變過,如果沒有改變過,就寫入,否則,生成一個新的時間戳并再次更新數(shù)據(jù),樂觀鎖其實并不是真正的鎖機制,它只是一種思想,在這里并不會對它進行展開介紹。

  多版本和快照隔離

  通過維護多個版本的數(shù)據(jù),數(shù)據(jù)庫可以允許事務(wù)在數(shù)據(jù)被其他事務(wù)更新時對舊版本的數(shù)據(jù)進行讀取,很多數(shù)據(jù)庫都對這一機制進行了實現(xiàn);因為所有的讀操作不再需要等待寫鎖的釋放,所以能夠顯著地提升讀的性能,MySQL 和 PostgreSQL 都對這一機制進行自己的實現(xiàn),也就是 MVCC,雖然各自實現(xiàn)的方式有所不同,MySQL 就通過文章中提到的回滾日志實現(xiàn)了 MVCC,保證事務(wù)并行執(zhí)行時能夠不等待互斥鎖的釋放直接獲取數(shù)據(jù)。

  隔離性與原子性

  在這里就需要簡單提一下在在原子性一節(jié)中遇到的級聯(lián)回滾等問題了,如果一個事務(wù)對數(shù)據(jù)進行了寫入,這時就會獲取一個互斥鎖,其他的事務(wù)就想要獲得改行數(shù)據(jù)的讀鎖就必須等待寫鎖的釋放,自然就不會發(fā)生級聯(lián)回滾等問題了。

  不過在大多數(shù)的數(shù)據(jù)庫,比如 MySQL 中都使用了 MVCC 等特性,也就是正常的讀方法是不需要獲取鎖的,在想要對讀取的數(shù)據(jù)進行更新時需要使用 SELECT ... FOR UPDATE 嘗試獲取對應(yīng)行的互斥鎖,以保證不同事務(wù)可以正常工作。

  一致性

  作者認為數(shù)據(jù)庫的一致性是一個非常讓人迷惑的概念,原因是數(shù)據(jù)庫領(lǐng)域其實包含兩個一致性,一個是 ACID 中的一致性、另一個是 CAP 定義中的一致性。

  這兩個數(shù)據(jù)庫的一致性說的完全不是一個事情,很多很多人都對這兩者的概念有非常深的誤解,當我們在討論數(shù)據(jù)庫的一致性時,一定要清楚上下文的語義是什么,盡量明確的問出我們要討論的到底是 ACID 中的一致性還是 CAP 中的一致性。

  ACID

  數(shù)據(jù)庫對于 ACID 中的一致性的定義是這樣的:如果一個事務(wù)原子地在一個一致地數(shù)據(jù)庫中獨立運行,那么在它執(zhí)行之后,數(shù)據(jù)庫的狀態(tài)一定是一致的。對于這個概念,它的第一層意思就是對于數(shù)據(jù)完整性的約束,包括主鍵約束、引用約束以及一些約束檢查等等,在事務(wù)的執(zhí)行的前后以及過程中不會違背對數(shù)據(jù)完整性的約束,所有對數(shù)據(jù)庫寫入的操作都應(yīng)該是合法的,并不能產(chǎn)生不合法的數(shù)據(jù)狀態(tài)。

  A transaction must preserve database consistency - if a transaction is run atomically in isolation starting from a consistent database, the database must again be consistent at the end of the transaction.

  我們可以將事務(wù)理解成一個函數(shù),它接受一個外界的 SQL 輸入和一個一致的數(shù)據(jù)庫,它一定會返回一個一致的數(shù)據(jù)庫。

  而第二層意思其實是指邏輯上的對于開發(fā)者的要求,我們要在代碼中寫出正確的事務(wù)邏輯,比如銀行轉(zhuǎn)賬,事務(wù)中的邏輯不可能只扣錢或者只加錢,這是應(yīng)用層面上對于數(shù)據(jù)庫一致性的要求。

  Ensuring consistency for an individual transaction is the responsibility of the application programmer who codes the transaction. - Database System Concepts

  數(shù)據(jù)庫 ACID 中的一致性對事務(wù)的要求不止包含對數(shù)據(jù)完整性以及合法性的檢查,還包含應(yīng)用層面邏輯的正確。

  CAP 定理中的數(shù)據(jù)一致性,其實是說分布式系統(tǒng)中的各個節(jié)點中對于同一數(shù)據(jù)的拷貝有著相同的值;而 ACID 中的一致性是指數(shù)據(jù)庫的規(guī)則,如果 schema 中規(guī)定了一個值必須是唯一的,那么一致的系統(tǒng)必須確保在所有的操作中,該值都是唯一的,由此來看 CAP 和 ACID 對于一致性的定義有著根本性的區(qū)別。

關(guān)于MySQL中什么是事務(wù)與鎖就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,可以學(xué)到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。

向AI問一下細節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI