欧美一级特黄大片做受成人-亚洲成人一区二区电影-激情熟女一区二区三区-日韩专区欧美专区国产专区

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

本篇內(nèi)容主要講解“MySQL的中事務(wù)與鎖的實現(xiàn)”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學(xué)習(xí)“MySQL的中事務(wù)與鎖的實現(xiàn)”吧!

創(chuàng)新互聯(lián)建站專注于網(wǎng)站建設(shè)|成都網(wǎng)站維護|優(yōu)化|托管以及網(wǎng)絡(luò)推廣,積累了大量的網(wǎng)站設(shè)計與制作經(jīng)驗,為許多企業(yè)提供了網(wǎng)站定制設(shè)計服務(wù),案例作品覆蓋成都服務(wù)器托管等行業(yè)。能根據(jù)企業(yè)所處的行業(yè)與銷售的產(chǎn)品,結(jié)合品牌形象的塑造,量身制作品質(zhì)網(wǎng)站。

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ā)者解決和負責的,在絕大多數(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ù)進行重做,它們能保證兩點:

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

  2. 在事務(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ū)別。

總結(jié)

事務(wù)的 ACID 四大基本特性是保證數(shù)據(jù)庫能夠運行的基石,但是完全保證數(shù)據(jù)庫的 ACID,尤其是隔離性會對性能有比較大影響,在實際的使用中我們也會根據(jù)業(yè)務(wù)的需求對隔離性進行調(diào)整,除了隔離性,數(shù)據(jù)庫的原子性和持久性相信都是比較好理解的特性,前者保證數(shù)據(jù)庫的事務(wù)要么全部執(zhí)行、要么全部不執(zhí)行,后者保證了對數(shù)據(jù)庫的寫入都是持久存儲的、非易失的,而一致性不僅是數(shù)據(jù)庫對本身數(shù)據(jù)的完整性的要求,同時也對開發(fā)者提出了要求 - 寫出邏輯正確并且合理的事務(wù)。

最后,也是最重要的,當別人在將一致性的時候,一定要搞清楚他的上下文,如果對文章的內(nèi)容有疑問,可以在評論中留言。

淺談數(shù)據(jù)庫并發(fā)控制 - 鎖和 MVCC

轉(zhuǎn)自 https://draveness.me/database-concurrency-control

在學(xué)習(xí)幾年編程之后,你會發(fā)現(xiàn)所有的問題都沒有簡單、快捷的解決方案,很多問題都需要權(quán)衡和妥協(xié),而本文介紹的就是數(shù)據(jù)庫在并發(fā)性能和可串行化之間做的權(quán)衡和妥協(xié) - 并發(fā)控制機制。

如果數(shù)據(jù)庫中的所有事務(wù)都是串行執(zhí)行的,那么它非常容易成為整個應(yīng)用的性能瓶頸,雖然說沒法水平擴展的節(jié)點在最后都會成為瓶頸,但是串行執(zhí)行事務(wù)的數(shù)據(jù)庫會加速這一過程;而并發(fā)(Concurrency)使一切事情的發(fā)生都有了可能,它能夠解決一定的性能問題,但是它會帶來更多詭異的錯誤。

引入了并發(fā)事務(wù)之后,如果不對事務(wù)的執(zhí)行進行控制就會出現(xiàn)各種各樣的問題,你可能沒有享受到并發(fā)帶來的性能提升就已經(jīng)被各種奇怪的問題折磨的欲仙欲死了。

概述

如何控制并發(fā)是數(shù)據(jù)庫領(lǐng)域中非常重要的問題之一,不過到今天為止事務(wù)并發(fā)的控制已經(jīng)有了很多成熟的解決方案,而這些方案的原理就是這篇文章想要介紹的內(nèi)容,文章中會介紹最為常見的三種并發(fā)控制機制:

分別是悲觀并發(fā)控制、樂觀并發(fā)控制和多版本并發(fā)控制,其中悲觀并發(fā)控制其實是最常見的并發(fā)控制機制,也就是鎖;而樂觀并發(fā)控制其實也有另一個名字:樂觀鎖,樂觀鎖其實并不是一種真實存在的鎖,我們會在文章后面的部分中具體介紹;最后就是多版本并發(fā)控制(MVCC)了,與前兩者對立的命名不同,MVCC 可以與前兩者中的任意一種機制結(jié)合使用,以提高數(shù)據(jù)庫的讀性能。

既然這篇文章介紹了不同的并發(fā)控制機制,那么一定會涉及到不同事務(wù)的并發(fā),我們會通過示意圖的方式分析各種機制是如何工作的。

悲觀并發(fā)控制

控制不同的事務(wù)對同一份數(shù)據(jù)的獲取是保證數(shù)據(jù)庫的一致性的最根本方法,如果我們能夠讓事務(wù)在同一時間對同一資源有著獨占的能力,那么就可以保證操作同一資源的不同事務(wù)不會相互影響。

最簡單的、應(yīng)用最廣的方法就是使用鎖來解決,當事務(wù)需要對資源進行操作時需要先獲得資源對應(yīng)的鎖,保證其他事務(wù)不會訪問該資源后,在對資源進行各種操作;在悲觀并發(fā)控制中,數(shù)據(jù)庫程序?qū)τ跀?shù)據(jù)被修改持悲觀的態(tài)度,在數(shù)據(jù)處理的過程中都會被鎖定,以此來解決競爭的問題。

讀寫鎖

為了最大化數(shù)據(jù)庫事務(wù)的并發(fā)能力,數(shù)據(jù)庫中的鎖被設(shè)計為兩種模式,分別是共享鎖和互斥鎖。當一個事務(wù)獲得共享鎖之后,它只可以進行讀操作,所以共享鎖也叫讀鎖;而當一個事務(wù)獲得一行數(shù)據(jù)的互斥鎖時,就可以對該行數(shù)據(jù)進行讀和寫操作,所以互斥鎖也叫寫鎖。

共享鎖和互斥鎖除了限制事務(wù)能夠執(zhí)行的讀寫操作之外,它們之間還有『共享』和『互斥』的關(guān)系,也就是多個事務(wù)可以同時獲得某一行數(shù)據(jù)的共享鎖,但是互斥鎖與共享鎖和其他的互斥鎖并不兼容,我們可以很自然地理解這么設(shè)計的原因:多個事務(wù)同時寫入同一數(shù)據(jù)難免會發(fā)生各種詭異的問題。

如果當前事務(wù)沒有辦法獲取該行數(shù)據(jù)對應(yīng)的鎖時就會陷入等待的狀態(tài),直到其他事務(wù)將當前數(shù)據(jù)對應(yīng)的鎖釋放才可以獲得鎖并執(zhí)行相應(yīng)的操作。

兩階段鎖協(xié)議

兩階段鎖協(xié)議(2PL)是一種能夠保證事務(wù)可串行化的協(xié)議,它將事務(wù)的獲取鎖和釋放鎖劃分成了增長(Growing)和縮減(Shrinking)兩個不同的階段。

在增長階段,一個事務(wù)可以獲得鎖但是不能釋放鎖;而在縮減階段事務(wù)只可以釋放鎖,并不能獲得新的鎖,如果只看 2PL 的定義,那么到這里就已經(jīng)介紹完了,但是它還有兩個變種:

  1. Strict 2PL:事務(wù)持有的互斥鎖必須在提交后再釋放;

  2. Rigorous 2PL:事務(wù)持有的所有鎖必須在提交后釋放;

雖然鎖的使用能夠為我們解決不同事務(wù)之間由于并發(fā)執(zhí)行造成的問題,但是兩階段鎖的使用卻引入了另一個嚴重的問題,死鎖;不同的事務(wù)等待對方已經(jīng)鎖定的資源就會造成死鎖,我們在這里舉一個簡單的例子:

兩個事務(wù)在剛開始時分別獲取了 draven 和 beacon 資源面的鎖,然后再請求對方已經(jīng)獲得的鎖時就會發(fā)生死鎖,雙方都沒有辦法等到鎖的釋放,如果沒有死鎖的處理機制就會無限等待下去,兩個事務(wù)都沒有辦法完成。

死鎖的處理

死鎖在多線程編程中是經(jīng)常遇到的事情,一旦涉及多個線程對資源進行爭奪就需要考慮當前的幾個線程或者事務(wù)是否會造成死鎖;解決死鎖大體來看有兩種辦法,一種是從源頭杜絕死鎖的產(chǎn)生和出現(xiàn),另一種是允許系統(tǒng)進入死鎖的狀態(tài),但是在系統(tǒng)出現(xiàn)死鎖時能夠及時發(fā)現(xiàn)并且進行恢復(fù)。

預(yù)防死鎖

有兩種方式可以幫助我們預(yù)防死鎖的出現(xiàn),一種是保證事務(wù)之間的等待不會出現(xiàn)環(huán),也就是事務(wù)之間的等待圖應(yīng)該是一張有向無環(huán)圖,沒有循環(huán)等待的情況或者保證一個事務(wù)中想要獲得的所有資源都在事務(wù)開始時以原子的方式被鎖定,所有的資源要么被鎖定要么都不被鎖定。

但是這種方式有兩個問題,在事務(wù)一開始時很難判斷哪些資源是需要鎖定的,同時因為一些很晚才會用到的數(shù)據(jù)被提前鎖定,數(shù)據(jù)的利用率與事務(wù)的并發(fā)率也非常的低。一種解決的辦法就是按照一定的順序為所有的數(shù)據(jù)行加鎖,同時與 2PL 協(xié)議結(jié)合,在加鎖階段保證所有的數(shù)據(jù)行都是從小到大依次進行加鎖的,不過這種方式依然需要事務(wù)提前知道將要加鎖的數(shù)據(jù)集。

另一種預(yù)防死鎖的方法就是使用搶占加事務(wù)回滾的方式預(yù)防死鎖,當事務(wù)開始執(zhí)行時會先獲得一個時間戳,數(shù)據(jù)庫程序會根據(jù)事務(wù)的時間戳決定事務(wù)應(yīng)該等待還是回滾,在這時也有兩種機制供我們選擇,一種是 wait-die 機制:

當執(zhí)行事務(wù)的時間戳小于另一事務(wù)時,即事務(wù) A 先于 B 開始,那么它就會等待另一個事務(wù)釋放對應(yīng)資源的鎖,否則就會保持當前的時間戳并回滾。

另一種機制叫做 wound-wait,這是一種搶占的解決方案,它和 wait-die 機制的結(jié)果完全相反,當前事務(wù)如果先于另一事務(wù)執(zhí)行并請求了另一事務(wù)的資源,那么另一事務(wù)會立刻回滾,將資源讓給先執(zhí)行的事務(wù),否則就會等待其他事務(wù)釋放資源:

兩種方法都會造成不必要的事務(wù)回滾,由此會帶來一定的性能損失,更簡單的解決死鎖的方式就是使用超時時間,但是超時時間的設(shè)定是需要仔細考慮的,否則會造成耗時較長的事務(wù)無法正常執(zhí)行,或者無法及時發(fā)現(xiàn)需要解決的死鎖,所以它的使用還是有一定的局限性。

死鎖檢測和恢復(fù)

如果數(shù)據(jù)庫程序無法通過協(xié)議從原理上保證死鎖不會發(fā)生,那么就需要在死鎖發(fā)生時及時檢測到并從死鎖狀態(tài)恢復(fù)到正常狀態(tài)保證數(shù)據(jù)庫程序可以正常工作。在使用檢測和恢復(fù)的方式解決死鎖時,數(shù)據(jù)庫程序需要維護數(shù)據(jù)和事務(wù)之間的引用信息,同時也需要提供一個用于判斷當前數(shù)據(jù)庫是否進入死鎖狀態(tài)的算法,最后需要在死鎖發(fā)生時提供合適的策略及時恢復(fù)。

在上一節(jié)中我們其實提到死鎖的檢測可以通過一個有向的等待圖來進行判斷,如果一個事務(wù)依賴于另一個事務(wù)正在處理的數(shù)據(jù),那么當前事務(wù)就會等待另一個事務(wù)的結(jié)束,這也就是整個等待圖中的一條邊:

如上圖所示,如果在這個有向圖中出現(xiàn)了環(huán),就說明當前數(shù)據(jù)庫進入了死鎖的狀態(tài) TransB -> TransE -> TransF -> TransD -> TransB,在這時就需要死鎖恢復(fù)機制接入了。

如何從死鎖中恢復(fù)其實非常簡單,最常見的解決辦法就是選擇整個環(huán)中一個事務(wù)進行回滾,以打破整個等待圖中的環(huán),在整個恢復(fù)的過程中有三個事情需要考慮:

每次出現(xiàn)死鎖時其實都會有多個事務(wù)被波及,而選擇其中哪一個任務(wù)進行回滾是必須要做的事情,在選擇犧牲品(Victim)時的黃金原則就是最小化代價,所以我們需要綜合考慮事務(wù)已經(jīng)計算的時間、使用的數(shù)據(jù)行以及涉及的事務(wù)等因素;當我們選擇了犧牲品之后就可以開始回滾了,回滾其實有兩種選擇一種是全部回滾,另一種是部分回滾,部分回滾會回滾到事務(wù)之前的一個檢查點上,如果沒有檢查點那自然沒有辦法進行部分回滾。

在死鎖恢復(fù)的過程中,其實還可能出現(xiàn)某些任務(wù)在多次死鎖時都被選擇成為犧牲品,一直都不會成功執(zhí)行,造成饑餓(Starvation),我們需要保證事務(wù)會在有窮的時間內(nèi)執(zhí)行,所以要在選擇犧牲品時將時間戳加入考慮的范圍。

鎖的粒度

到目前為止我們都沒有對不同粒度的鎖進行討論,一直以來我們都討論的都是數(shù)據(jù)行鎖,但是在有些時候我們希望將多個節(jié)點看做一個數(shù)據(jù)單元,使用鎖直接將這個數(shù)據(jù)單元、表甚至數(shù)據(jù)庫鎖定起來。這個目標的實現(xiàn)需要我們在數(shù)據(jù)庫中定義不同粒度的鎖:

當我們擁有了不同粒度的鎖之后,如果某個事務(wù)想要鎖定整個數(shù)據(jù)庫或者整張表時只需要簡單的鎖住對應(yīng)的節(jié)點就會在當前節(jié)點加上顯示(explicit)鎖,在所有的子節(jié)點上加隱式(implicit)鎖;雖然這種不同粒度的鎖能夠解決父節(jié)點被加鎖時,子節(jié)點不能被加鎖的問題,但是我們沒有辦法在子節(jié)點被加鎖時,立刻確定父節(jié)點不能被加鎖。

在這時我們就需要引入意向鎖來解決這個問題了,當需要給子節(jié)點加鎖時,先給所有的父節(jié)點加對應(yīng)的意向鎖,意向鎖之間是完全不會互斥的,只是用來幫助父節(jié)點快速判斷是否可以對該節(jié)點進行加鎖:

這里是一張引入了兩種意向鎖,意向共享鎖和意向互斥鎖之后所有的鎖之間的兼容關(guān)系;到這里,我們通過不同粒度的鎖和意向鎖加快了數(shù)據(jù)庫的吞吐量。

樂觀并發(fā)控制

除了悲觀并發(fā)控制機制 - 鎖之外,我們其實還有其他的并發(fā)控制機制,樂觀并發(fā)控制(Optimistic Concurrency Control)。樂觀并發(fā)控制也叫樂觀鎖,但是它并不是真正的鎖,很多人都會誤以為樂觀鎖是一種真正的鎖,然而它只是一種并發(fā)控制的思想。

在這一節(jié)中,我們將會先介紹基于時間戳的并發(fā)控制機制,然后在這個協(xié)議的基礎(chǔ)上進行擴展,實現(xiàn)樂觀的并發(fā)控制機制。

基于時間戳的協(xié)議

鎖協(xié)議按照不同事務(wù)對同一數(shù)據(jù)項請求的時間依次執(zhí)行,因為后面執(zhí)行的事務(wù)想要獲取的數(shù)據(jù)已將被前面的事務(wù)加鎖,只能等待鎖的釋放,所以基于鎖的協(xié)議執(zhí)行事務(wù)的順序與獲得鎖的順序有關(guān)。在這里想要介紹的基于時間戳的協(xié)議能夠在事務(wù)執(zhí)行之前先決定事務(wù)的執(zhí)行順序。

每一個事務(wù)都會具有一個全局唯一的時間戳,它即可以使用系統(tǒng)的時鐘時間,也可以使用計數(shù)器,只要能夠保證所有的時間戳都是唯一并且是隨時間遞增的就可以。

基于時間戳的協(xié)議能夠保證事務(wù)并行執(zhí)行的順序與事務(wù)按照時間戳串行執(zhí)行的效果完全相同;每一個數(shù)據(jù)項都有兩個時間戳,讀時間戳和寫時間戳,分別代表了當前成功執(zhí)行對應(yīng)操作的事務(wù)的時間戳。

該協(xié)議能夠保證所有沖突的讀寫操作都能按照時間戳的大小串行執(zhí)行,在執(zhí)行對應(yīng)的操作時不需要關(guān)注其他的事務(wù)只需要關(guān)心數(shù)據(jù)項對應(yīng)時間戳的值就可以了:

無論是讀操作還是寫操作都會從左到右依次比較讀寫時間戳的值,如果小于當前值就會直接被拒絕然后回滾,數(shù)據(jù)庫系統(tǒng)會給回滾的事務(wù)添加一個新的時間戳并重新執(zhí)行這個事務(wù)。

基于驗證的協(xié)議

樂觀并發(fā)控制其實本質(zhì)上就是基于驗證的協(xié)議,因為在多數(shù)的應(yīng)用中只讀的事務(wù)占了絕大多數(shù),事務(wù)之間因為寫操作造成沖突的可能非常小,也就是說大多數(shù)的事務(wù)在不需要并發(fā)控制機制也能運行的非常好,也可以保證數(shù)據(jù)庫的一致性;而并發(fā)控制機制其實向整個數(shù)據(jù)庫系統(tǒng)添加了很多的開銷,我們其實可以通過別的策略降低這部分開銷。

而驗證協(xié)議就是我們找到的解決辦法,它根據(jù)事務(wù)的只讀或者更新將所有事務(wù)的執(zhí)行分為兩到三個階段:

在讀階段,數(shù)據(jù)庫會執(zhí)行事務(wù)中的全部讀操作和寫操作,并將所有寫后的值存入臨時變量中,并不會真正更新數(shù)據(jù)庫中的內(nèi)容;在這時候會進入下一個階段,數(shù)據(jù)庫程序會檢查當前的改動是否合法,也就是是否有其他事務(wù)在 RAED PHASE 期間更新了數(shù)據(jù),如果通過測試那么直接就進入 WRITE PHASE 將所有存在臨時變量中的改動全部寫入數(shù)據(jù)庫,沒有通過測試的事務(wù)會直接被終止。

為了保證樂觀并發(fā)控制能夠正常運行,我們需要知道一個事務(wù)不同階段的發(fā)生時間,包括事務(wù)開始時間、驗證階段的開始時間以及寫階段的結(jié)束時間;通過這三個時間戳,我們可以保證任意沖突的事務(wù)不會同時寫入數(shù)據(jù)庫,一旦由一個事務(wù)完成了驗證階段就會立即寫入,其他讀取了相同數(shù)據(jù)的事務(wù)就會回滾重新執(zhí)行。

作為樂觀的并發(fā)控制機制,它會假定所有的事務(wù)在最終都會通過驗證階段并且執(zhí)行成功,而鎖機制和基于時間戳排序的協(xié)議是悲觀的,因為它們會在發(fā)生沖突時強制事務(wù)進行等待或者回滾,哪怕有不需要鎖也能夠保證事務(wù)之間不會沖突的可能。

多版本并發(fā)控制

到目前為止我們介紹的并發(fā)控制機制其實都是通過延遲或者終止相應(yīng)的事務(wù)來解決事務(wù)之間的競爭條件(Race condition)來保證事務(wù)的可串行化;雖然前面的兩種并發(fā)控制機制確實能夠從根本上解決并發(fā)事務(wù)的可串行化的問題,但是在實際環(huán)境中數(shù)據(jù)庫的事務(wù)大都是只讀的,讀請求是寫請求的很多倍,如果寫請求和讀請求之前沒有并發(fā)控制機制,那么最壞的情況也是讀請求讀到了已經(jīng)寫入的數(shù)據(jù),這對很多應(yīng)用完全是可以接受的。

在這種大前提下,數(shù)據(jù)庫系統(tǒng)引入了另一種并發(fā)控制機制 - 多版本并發(fā)控制(Multiversion Concurrency Control),每一個寫操作都會創(chuàng)建一個新版本的數(shù)據(jù),讀操作會從有限多個版本的數(shù)據(jù)中挑選一個最合適的結(jié)果直接返回;在這時,讀寫操作之間的沖突就不再需要被關(guān)注,而管理和快速挑選數(shù)據(jù)的版本就成了 MVCC 需要解決的主要問題。

MVCC 并不是一個與樂觀和悲觀并發(fā)控制對立的東西,它能夠與兩者很好的結(jié)合以增加事務(wù)的并發(fā)量,在目前最流行的 SQL 數(shù)據(jù)庫 MySQL 和 PostgreSQL 中都對 MVCC 進行了實現(xiàn);但是由于它們分別實現(xiàn)了悲觀鎖和樂觀鎖,所以 MVCC 實現(xiàn)的方式也不同。

MySQL 與 MVCC

MySQL 中實現(xiàn)的多版本兩階段鎖協(xié)議(Multiversion 2PL)將 MVCC 和 2PL 的優(yōu)點結(jié)合了起來,每一個版本的數(shù)據(jù)行都具有一個唯一的時間戳,當有讀事務(wù)請求時,數(shù)據(jù)庫程序會直接從多個版本的數(shù)據(jù)項中具有最大時間戳的返回。

更新操作就稍微有些復(fù)雜了,事務(wù)會先讀取最新版本的數(shù)據(jù)計算出數(shù)據(jù)更新后的結(jié)果,然后創(chuàng)建一個新版本的數(shù)據(jù),新數(shù)據(jù)的時間戳是目前數(shù)據(jù)行的最大版本 +1:

數(shù)據(jù)版本的刪除也是根據(jù)時間戳來選擇的,MySQL 會將版本最低的數(shù)據(jù)定時從數(shù)據(jù)庫中清除以保證不會出現(xiàn)大量的遺留內(nèi)容。

PostgreSQL 與 MVCC

與 MySQL 中使用悲觀并發(fā)控制不同,PostgreSQL 中都是使用樂觀并發(fā)控制的,這也就導(dǎo)致了 MVCC 在于樂觀鎖結(jié)合時的實現(xiàn)上有一些不同,最終實現(xiàn)的叫做多版本時間戳排序協(xié)議(Multiversion Timestamp Ordering),在這個協(xié)議中,所有的的事務(wù)在執(zhí)行之前都會被分配一個唯一的時間戳,每一個數(shù)據(jù)項都有讀寫兩個時間戳:

當 PostgreSQL 的事務(wù)發(fā)出了一個讀請求,數(shù)據(jù)庫直接將最新版本的數(shù)據(jù)返回,不會被任何操作阻塞,而寫操作在執(zhí)行時,事務(wù)的時間戳一定要大或者等于數(shù)據(jù)行的讀時間戳,否則就會被回滾。

這種 MVCC 的實現(xiàn)保證了讀事務(wù)永遠都不會失敗并且不需要等待鎖的釋放,對于讀請求遠遠多于寫請求的應(yīng)用程序,樂觀鎖加 MVCC 對數(shù)據(jù)庫的性能有著非常大的提升;雖然這種協(xié)議能夠針對一些實際情況做出一些明顯的性能提升,但是也會導(dǎo)致兩個問題,一個是每一次讀操作都會更新讀時間戳造成兩次的磁盤寫入,第二是事務(wù)之間的沖突是通過回滾解決的,所以如果沖突的可能性非常高或者回滾代價巨大,數(shù)據(jù)庫的讀寫性能還不如使用傳統(tǒng)的鎖等待方式。

1. MVCC簡介與實踐

MySQL 在InnoDB引擎下有當前讀和快照讀兩種模式。

1 當前讀即加鎖讀,讀取記錄的最新版本號,會加鎖保證其他并發(fā)事物不能修改當前記錄,直至釋放鎖。插入/更新/刪除操作默認使用當前讀,顯示的為select語句加lock in share mode或for update的查詢也采用當前讀模式。

2 快照讀:不加鎖,讀取記錄的快照版本,而非最新版本,使用MVCC機制,最大的好處是讀取不需要加鎖,讀寫不沖突,用于讀操作多于寫操作的應(yīng)用,因此在不顯示加[lock in share mode]/[for update]的select語句,即普通的一條select語句默認都是使用快照讀MVCC實現(xiàn)模式。所以樓主的為了讓大家明白所做的演示操作,既有當前讀也有快照讀……

1.1 什么是MVCC

MVCC是一種多版本并發(fā)控制機制。

1.2 MVCC是為了解決什么問題?

  • 大多數(shù)的MYSQL事務(wù)型存儲引擎,如,InnoDB,F(xiàn)alcon以及PBXT都不使用一種簡單的行鎖機制.事實上,他們都和MVCC–多版本并發(fā)控制來一起使用.

  • 大家都應(yīng)該知道,鎖機制可以控制并發(fā)操作,但是其系統(tǒng)開銷較大,而MVCC可以在大多數(shù)情況下代替行級鎖,使用MVCC,能降低其系統(tǒng)開銷.

1.3 MVCC實現(xiàn)

MVCC是通過保存數(shù)據(jù)在某個時間點的快照來實現(xiàn)的. 不同存儲引擎的MVCC. 不同存儲引擎的MVCC實現(xiàn)是不同的,典型的有樂觀并發(fā)控制和悲觀并發(fā)控制.

2.MVCC 具體實現(xiàn)分析

下面,我們通過InnoDB的MVCC實現(xiàn)來分析MVCC使怎樣進行并發(fā)控制的.  InnoDB的MVCC,是通過在每行記錄后面保存兩個隱藏的列來實現(xiàn)的,這兩個列,分別保存了這個行的創(chuàng)建時間,一個保存的是行的刪除時間。這里存儲的并不是實際的時間值,而是系統(tǒng)版本號(可以理解為事務(wù)的ID),沒開始一個新的事務(wù),系統(tǒng)版本號就會自動遞增,事務(wù)開始時刻的系統(tǒng)版本號會作為事務(wù)的ID.下面看一下在REPEATABLE READ隔離級別下,MVCC具體是如何操作的.

2.1簡單的小例子

create table yang(  id int primary key auto_increment,  name varchar(20));

假設(shè)系統(tǒng)的版本號從1開始.

INSERT

InnoDB為新插入的每一行保存當前系統(tǒng)版本號作為版本號.  第一個事務(wù)ID為1;

start transaction; insert into yang values(NULL,'yang')  ; insert into yang values(NULL,'long'); insert into yang values(NULL,'fei'); commit;

對應(yīng)在數(shù)據(jù)中的表如下(后面兩列是隱藏列,我們通過查詢語句并看不到)

idname創(chuàng)建時間(事務(wù)ID)刪除時間(事務(wù)ID)
1yang1undefined
2long1undefined
3fei1undefined

SELECT

InnoDB會根據(jù)以下兩個條件檢查每行記錄:  a.InnoDB只會查找版本早于當前事務(wù)版本的數(shù)據(jù)行(也就是,行的系統(tǒng)版本號小于或等于事務(wù)的系統(tǒng)版本號),這樣可以確保事務(wù)讀取的行,要么是在事務(wù)開始前已經(jīng)存在的,要么是事務(wù)自身插入或者修改過的.  b.行的刪除版本要么未定義,要么大于當前事務(wù)版本號,這可以確保事務(wù)讀取到的行,在事務(wù)開始之前未被刪除.  只有a,b同時滿足的記錄,才能返回作為查詢結(jié)果.

DELETE

InnoDB會為刪除的每一行保存當前系統(tǒng)的版本號(事務(wù)的ID)作為刪除標識.  看下面的具體例子分析:  第二個事務(wù),ID為2;

start transaction; select *  from yang;  //(1) select *  from yang;  //(2) commit;

假設(shè)1

假設(shè)在執(zhí)行這個事務(wù)ID為2的過程中,剛執(zhí)行到(1),這時,有另一個事務(wù)ID為3往這個表里插入了一條數(shù)據(jù);  第三個事務(wù)ID為3;

start transaction; insert into yang values(NULL,'tian'); commit;

這時表中的數(shù)據(jù)如下:

idname創(chuàng)建時間(事務(wù)ID)刪除時間(事務(wù)ID)
1yang1undefined
2long1undefined
3fei1undefined
4tian3undefined

然后接著執(zhí)行事務(wù)2中的(2),由于id=4的數(shù)據(jù)的創(chuàng)建時間(事務(wù)ID為3),執(zhí)行當前事務(wù)的ID為2,而InnoDB只會查找事務(wù)ID小于等于當前事務(wù)ID的數(shù)據(jù)行,所以id=4的數(shù)據(jù)行并不會在執(zhí)行事務(wù)2中的(2)被檢索出來,在事務(wù)2中的兩條select 語句檢索出來的數(shù)據(jù)都只會下表:

idname創(chuàng)建時間(事務(wù)ID)刪除時間(事務(wù)ID)
1yang1undefined
2long1undefined
3fei1undefined

假設(shè)2

假設(shè)在執(zhí)行這個事務(wù)ID為2的過程中,剛執(zhí)行到(1),假設(shè)事務(wù)執(zhí)行完事務(wù)3后,接著又執(zhí)行了事務(wù)4;  第四個事務(wù):

start   transaction;  delete  from yang where id=1; commit;

此時數(shù)據(jù)庫中的表如下:

idname創(chuàng)建時間(事務(wù)ID)刪除時間(事務(wù)ID)
1yang14
2long1undefined
3fei1undefined
4tian3undefined

接著執(zhí)行事務(wù)ID為2的事務(wù)(2),根據(jù)SELECT 檢索條件可以知道,它會檢索創(chuàng)建時間(創(chuàng)建事務(wù)的ID)小于當前事務(wù)ID的行和刪除時間(刪除事務(wù)的ID)大于當前事務(wù)的行,而id=4的行上面已經(jīng)說過,而id=1的行由于刪除時間(刪除事務(wù)的ID)大于當前事務(wù)的ID,所以事務(wù)2的(2)select * from yang也會把id=1的數(shù)據(jù)檢索出來.所以,事務(wù)2中的兩條select 語句檢索出來的數(shù)據(jù)都如下:

idname創(chuàng)建時間(事務(wù)ID)刪除時間(事務(wù)ID)
1yang14
2long1undefined
3fei1undefined

UPDATE

InnoDB執(zhí)行UPDATE,實際上是新插入了一行記錄,并保存其創(chuàng)建時間為當前事務(wù)的ID,同時保存當前事務(wù)ID到要UPDATE的行的刪除時間.

假設(shè)3

假設(shè)在執(zhí)行完事務(wù)2的(1)后又執(zhí)行,其它用戶執(zhí)行了事務(wù)3,4,這時,又有一個用戶對這張表執(zhí)行了UPDATE操作:  第5個事務(wù):

start  transaction; update yang set name='Long' where id=2; commit;

根據(jù)update的更新原則:會生成新的一行,并在原來要修改的列的刪除時間列上添加本事務(wù)ID,得到表如下:

idname創(chuàng)建時間(事務(wù)ID)刪除時間(事務(wù)ID)
1yang14
2long15
3fei1undefined
4tian3undefined
2Long5undefined

繼續(xù)執(zhí)行事務(wù)2的(2),根據(jù)select 語句的檢索條件,得到下表:

idname創(chuàng)建時間(事務(wù)ID)刪除時間(事務(wù)ID)
1yang14
2long15
3fei1undefined

還是和事務(wù)2中(1)select 得到相同的結(jié)果.

到此,相信大家對“MySQL的中事務(wù)與鎖的實現(xiàn)”有了更深的了解,不妨來實際操作一番吧!這里是創(chuàng)新互聯(lián)網(wǎng)站,更多相關(guān)內(nèi)容可以進入相關(guān)頻道進行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!

分享名稱:MySQL的中事務(wù)與鎖的實現(xiàn)
網(wǎng)頁地址:http://aaarwkj.com/article26/pjcsjg.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站建設(shè)、全網(wǎng)營銷推廣、動態(tài)網(wǎng)站電子商務(wù)、App設(shè)計、手機網(wǎng)站建設(shè)

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

網(wǎng)站建設(shè)網(wǎng)站維護公司
日韩一级久久精品理论| 成人av在线免费播放| 日本视频一曲二曲三曲四曲| 亚洲精品福利一二三区| 久久人人97超碰人人爱一久久精品| 日韩高清精品一区二区| 一区二区三区都市激情| 在线免费观看日韩黄片| 欧美日韩一区二区黄色| 色噜噜色一区二区三区| 精品国产av一区二区麻豆| 片子免费毛片日韩不卡一区| 亚洲av男人天堂一区| 国产一区二区毛多内射| 欧美日韩国产精品高清| 精品一区二区三区高清| 日本精品专区在线观看| 欧美日本国产在线一区二区| 久草尤物视频在线观看| 亚洲男人成人性天堂网站| 国产一级一片内射视频 | 精品人妻中文av一区二区| 亚洲欧美国产日韩综合在线| 日本色电影一区二区三区| 亚洲黄色录像特级生活片| 日韩av一区二区久久久| 国产三级尤物在线观看| 色男人天堂网在线视频| 九九六热这里只有精品| 欧美高清一区二区三区精品| 日韩精品一区二区三区人妻视频| 大屁股白浆一区二区三区| 九九九热精品在线视频观看| 亚洲国产精品欧美激情| 国产婷婷综合一区二区| 欧美 日韩一区二区在线| 囗交囗爆吞精在线视频| 中文在线在线天堂中文| 国产精品久久久久久久av三级| 国产午夜亚洲精品羞羞网站| 国产综合欧美日韩在线91|