<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          面試官靈魂的一擊:你懂MySQL事務(wù)嗎?

          共 8787字,需瀏覽 18分鐘

           ·

          2020-12-31 16:11


          • 概念
          • 隔離性與隔離級別
          • 事務(wù)隔離的實現(xiàn)
          • 事務(wù)啟動方式
          • MVCC工作原理
          • 總結(jié)

          一、概念

          事務(wù)到底是什么東西呢?想必大家學(xué)習(xí)的時候也是對事務(wù)的概念很模糊的。接下來通過一個經(jīng)典例子講解事務(wù)。

          銀行在兩個賬戶之間轉(zhuǎn)賬,從A賬戶轉(zhuǎn)入B賬戶1000元,系統(tǒng)先減少A賬戶的1000元,然后再為B賬號增加1000元。如果全部執(zhí)行成功,數(shù)據(jù)庫處于一致性;

          如果僅執(zhí)行完A賬戶金額的修改,而沒有增加B賬戶的金額,則數(shù)據(jù)庫就處于不一致狀態(tài),這時就需要取消前面的操作。

          這過程中會有一系列的操作,比如余額查詢,余額做加減法,更新余額等,這些操作必須保證是一個整體執(zhí)行,要么全部成功,要么全部失敗,不能讓A賬戶錢扣了,但是中途某些操作失敗了,導(dǎo)致B賬戶更新余額失敗。這樣用戶就不樂意了,銀行這不是坑我嗎?

          事務(wù)就是要保證一組數(shù)據(jù)庫操作,要么全部成功,要么全部失敗。

          在MySQL中,事務(wù)支持是在引擎層實現(xiàn)的。你現(xiàn)在知道,MySQL是一個支持多引擎的系統(tǒng),但并不是所有的引擎都支持事務(wù)。

          比如MySQL原生的MyISAM引擎就不支持事務(wù),這也是MyISAM被InnoDB取代的重要原因之一。

          接下來會以InnoDB為例,抽絲剝繭MySQL在事務(wù)支持方面的特定實現(xiàn)。

          二、隔離性與隔離級別

          提到事務(wù),你肯定會想到ACIDAtomicityConsistency、Isolation、Durability,即原子性、一致性、隔離性、持久性),接下來我們就要講解其中的I,也就是隔離性

          當(dāng)數(shù)據(jù)庫上存在多個事務(wù)同時執(zhí)行的時候,就可能出現(xiàn)臟讀(dirty read)、不可重復(fù)讀(non-repeatable read)、幻讀(phantom read)的問題,為了解決這些問題,就有了隔離級別的概念。

          我們知道,隔離級別越高,效率就越低,因此我們很多情況下需要在二者之間找到一個平衡點。

          SQL標(biāo)準(zhǔn)的事務(wù)隔離級別包括:

          1. 讀未提交(read uncommitted)
          2. 讀提交(read committed)
          3. 可重復(fù)讀(repeatable read)
          4. 串行化(serializable )

          下面我逐一為你解釋:

          1. 讀未提交:事務(wù)中的修改,即使沒有提交,對其他事務(wù)也都是可見的,事務(wù)可以讀取未提交的數(shù)據(jù),也被稱為臟讀。這個級別會導(dǎo)致很多問題,從性能上來說也不會比其他隔離級別好很多,但卻缺乏其他級別的很多好處,一般實際應(yīng)用中很少用,甚至有些數(shù)據(jù)庫內(nèi)部根本就沒有實現(xiàn)。

          2. 讀已提交:事務(wù)從開始直到提交之前,所做的任何修改對其他事務(wù)都是不可見的,這個級別有時候也叫做不可重復(fù)讀(Nonrepeatable Read),因為同一事務(wù)中兩次執(zhí)行同樣的查詢,可能會得到不一樣的結(jié)果

          3. 可重復(fù)度:同個事務(wù)中多次查詢結(jié)果是一致的,解決了不可重復(fù)讀的問題。此隔離級別下還是無法解決另外一個幻讀(Phantom Read)的問題,幻讀是指當(dāng)某個事務(wù)在讀取某個范圍內(nèi)的記錄時,另外一個事務(wù)又在該范圍內(nèi)插入了新的記錄,之前的事務(wù)再次讀取該范圍的記錄時,會產(chǎn)生幻行

          4. 串行化:顧名思義是對于同一行記錄,會加寫鎖,會加讀鎖。當(dāng)出現(xiàn)讀寫鎖沖突的時候,后訪問的事務(wù)必須等前一個事務(wù)執(zhí)行完成,才能繼續(xù)執(zhí)行。

          對于上面的概念中,可能讀已提交可重復(fù)讀比較難理解,下面會用一個例子說明這種集中隔離級別。假設(shè)數(shù)據(jù)表T中只有一列,其中一行的值為1,下面是按照時間順序執(zhí)行兩個事務(wù)的行為。

          mysql>?create?table?T(c?int)?engine=InnoDB;
          insert?into?T(c)?values(1);

          接下來講解不同的隔離級別下,事務(wù)A會有哪些不同的返回結(jié)果,也就是圖里面V1、V2、V3的返回值分別是什么。

          1. 若隔離級別是讀未提交, 則V1的值就是2。這時候事務(wù)B雖然還沒有提交,但是結(jié)果已經(jīng)被A看到了。因此,V2、V3也都是2。

          2. 若隔離級別是讀提交,則V1是1,V2的值是2。事務(wù)B的更新在提交后才能被A看到。所以, V3的值也是2。

          3. 若隔離級別是可重復(fù)讀,則V1、V2是1,V3是2。之所以V2還是1,遵循的就是這個要求:事務(wù)在執(zhí)行期間看到的數(shù)據(jù)前后必須是一致的。

          4. 若隔離級別是串行化,則在事務(wù)B執(zhí)行“將1改成2”的時候,會被鎖住。直到事務(wù)A提交后,事務(wù)B才可以繼續(xù)執(zhí)行。所以從A的角度看,V1、V2值是1,V3的值是2。

          在實現(xiàn)上,數(shù)據(jù)庫里面會創(chuàng)建一個視圖,訪問的時候以視圖的邏輯結(jié)果為準(zhǔn)。在可重復(fù)讀隔離級別下,這個視圖是在事務(wù)啟動時創(chuàng)建的,整個事務(wù)存在期間都用這個視圖。

          讀提交隔離級別下,這個視圖是在每個SQL語句開始執(zhí)行的時候創(chuàng)建的。這里需要注意的是,讀未提交隔離級別下直接返回記錄上的最新值,沒有視圖概念;而串行化隔離級別下直接用加鎖的方式來避免并行訪問。

          注意一下,每種數(shù)據(jù)庫的行為會有所不一樣,Oracle數(shù)據(jù)庫的默認(rèn)隔離界別是讀提交,因此,當(dāng)我們需要進行不同數(shù)據(jù)庫種類之間遷移的時候,為了保證數(shù)據(jù)庫隔離級別的一致,切記將MYSQL的隔離級別設(shè)置為讀提交。

          配置的方式是,將啟動參數(shù)transaction-isolation的值設(shè)置成READ-COMMITTED。你可以用show variables來查看當(dāng)前的值。

          每種隔離級別都有它自己的使用場景,你要根據(jù)自己的業(yè)務(wù)情況來定。我想你可能會問那什么時候需要“可重復(fù)讀”的場景呢?我們來看一個數(shù)據(jù)校對邏輯的案例。

          假設(shè)你在管理一個個人銀行賬戶表。一個表存了每個月月底的余額,一個表存了賬單明細(xì)。這時候你要做數(shù)據(jù)校對,也就是判斷上個月的余額和當(dāng)前余額的差額,是否與本月的賬單明細(xì)一致。你一定希望在校對過程中,即使有用戶發(fā)生了一筆新的交易,也不影響你的校對結(jié)果。

          這時候使用可重復(fù)讀隔離級別就很方便。事務(wù)啟動時的視圖可以認(rèn)為是靜態(tài)的,不受其他事務(wù)更新的影響。

          三、事務(wù)隔離的實現(xiàn)

          接下來以可重復(fù)度來展開事務(wù)隔離具體是怎么實現(xiàn)的。

          在MySQL中,實際上每條記錄在更新的時候都會同時記錄一條回滾操作。記錄上的最新值,通過回滾操作,都可以得到前一個狀態(tài)的值。

          假設(shè)一個值從1被按順序改成了2、3、4,在回滾日志里面就會有類似下面的記錄。

          可以看到當(dāng)前值是4,從圖中可以看到在查詢的時候,不同時刻啟動的事務(wù)會有不同的read-view。如圖中看到的,在視圖AB、C里面,這一個記錄的值分別是1、2、4,同一條記錄在系統(tǒng)中可以存在多個版本,就是數(shù)據(jù)庫的多版本并發(fā)控制(MVCC)。

          對于read-view A,要得到1,就必須將當(dāng)前值依次執(zhí)行圖中所有的回滾操作得到。同時你會發(fā)現(xiàn),即使現(xiàn)在有另外一個事務(wù)正在將4改成5,這個事務(wù)跟read-view A、B、C對應(yīng)的事務(wù)是不會沖突的。

          你一定會問,回滾日志總不能一直保留吧,什么時候刪除呢?

          這是肯定不能一直保留的,在不需要的時候才刪除。系統(tǒng)會判斷,當(dāng)沒有事務(wù)再需要用到這些回滾日志時,回滾日志會被刪除。

          那么什么時候才不需要了呢?就是當(dāng)系統(tǒng)里沒有比這個回滾日志更早的read-view的時候。

          基于上面的說明,我們來討論一下為什么建議你盡量不要使用長事務(wù)。

          長事務(wù)意味著系統(tǒng)里面會存在很老的事務(wù)視圖。由于這些事務(wù)隨時可能訪問數(shù)據(jù)庫里面的任何數(shù)據(jù),所以這個事務(wù)提交之前,數(shù)據(jù)庫里面它可能用到的回滾記錄都必須保留,這就會導(dǎo)致大量占用存儲空間。

          MySQL 5.5及以前的版本,回滾日志是跟數(shù)據(jù)字典一起放在ibdata文件里的,即使長事務(wù)最終提交,回滾段被清理,文件也不會變小。我見過數(shù)據(jù)只有20GB,而回滾段有200GB的庫。最終只好為了清理回滾段,重建整個庫。

          除了對回滾段的影響,長事務(wù)還占用鎖資源,也可能拖垮整個庫,這個我們會在后面講鎖的時候展開。

          四、事務(wù)啟動方式

          MySQL的事務(wù)啟動方式有以下幾種:

          1. 顯式啟動事務(wù)語句,beginstart transaction。配套的提交語句是commit,回滾語句是rollback。
          2. set autocommit=0,這個命令會將這個線程的自動提交關(guān)掉。意味著如果你只執(zhí)行一個select語句,這個事務(wù)就啟動了,而且并不會自動提交。這個事務(wù)持續(xù)存在直到你主動執(zhí)行commitrollback語句,或者斷開連接。

          有些客戶端連接框架會默認(rèn)連接成功后先執(zhí)行一個set autocommit=0的命令。這就導(dǎo)致接下來的查詢都在事務(wù)中,如果是長連接,就導(dǎo)致了意外的長事務(wù)。

          因此,我會建議你總是使用set autocommit=1, 通過顯式語句的方式來啟動事務(wù)。

          但是有的開發(fā)同學(xué)會糾結(jié)多一次交互的問題。對于一個需要頻繁使用事務(wù)的業(yè)務(wù),第二種方式每個事務(wù)在開始時都不需要主動執(zhí)行一次begin,減少了語句的交互次數(shù)。如果你也有這個顧慮,我建議你使用commit work and chain語法。

          autocommit為1的情況下,用begin顯式啟動的事務(wù),如果執(zhí)行commit則提交事務(wù)。如果執(zhí)行commit work and chain,則是提交事務(wù)并自動啟動下一個事務(wù),這樣也省去了再次執(zhí)行begin語句的開銷。同時帶來的好處是從程序開發(fā)的角度明確地知道每個語句是否處于事務(wù)中。

          你可以在information_schema庫的innodb_trx這個表中查詢長事務(wù),比如下面這個語句,用于查找持續(xù)時間超過60s的事務(wù)。

          select?*?from?information_schema.innodb_trx?where?TIME_TO_SEC(timediff(now(),trx_started))>60

          五、MVCC工作原理

          可重復(fù)讀隔離級別下,事務(wù)在啟動的時候就“拍了個快照”。請注意,這個快照是基于整個庫的,這時候你肯定覺得不可思議,如果一個庫上百G的數(shù)據(jù),那么我啟動一個事務(wù),那MYSQL豈不是要將上百G的數(shù)據(jù)拷貝出來,這個過程不是非常慢嗎?但是為什么我們平時并沒有感覺到它??呢?

          事實上,我們并不需要拷貝出這100G的數(shù)據(jù)。

          我們先來看看這個快照是怎么實現(xiàn)的。InnoDB里面每個事務(wù)有一個唯一的事務(wù)ID,叫作transaction id。它是在事務(wù)開始的時候向InnoDB的事務(wù)系統(tǒng)申請的,是按申請順序嚴(yán)格遞增的。

          每次事務(wù)更新數(shù)據(jù)的時候,都會生成一個新的數(shù)據(jù)版本,并且把transaction id賦值給這個數(shù)據(jù)版本的事務(wù)ID,記為row trx_id。同時,舊的數(shù)據(jù)版本要保留,并且在新的數(shù)據(jù)版本中,能夠有信息可以直接拿到它。這也說明了,數(shù)據(jù)表中的一行記錄,可能存在多個版本(row),每個版本有自己的row_trx_id.

          下面用一張圖說明一個記錄被多個事務(wù)連續(xù)更新后的狀態(tài),如下圖所示:

          圖中用打括號表示一行數(shù)據(jù)的4個版本,當(dāng)前最新版本是V4,k的值是12,它是被transaction id為25的事務(wù)更新的,因此它的row trx_id也是25。

          你可能會問,前面的文章不是說,語句更新會生成undo log(回滾日志)嗎?那么,undo log在哪呢?

          實際上,圖2中的三個虛線箭頭,就是undo log;而V1、V2、V3并不是物理上真實存在的,而是每次需要的時候根據(jù)當(dāng)前版本和undo log計算出來的。比如,需要V2的時候,就是通過V4依次執(zhí)行U3、U2算出來。

          明白了多版本和row trx_id的概念后,我們再來想一下,InnoDB是怎么定義那個“100G”的快照的。

          按照可重復(fù)讀的定義,一個事務(wù)啟動的時候,能夠看到所有已經(jīng)提交的事務(wù)結(jié)果。但是之后,這個事務(wù)執(zhí)行期間,其他事務(wù)的更新對它不可見。

          因此,一個事務(wù)只需要在啟動的時候聲明說,以我啟動的時刻為準(zhǔn),如果一個數(shù)據(jù)版本是在我啟動之前生成的,就認(rèn);如果是我啟動以后才生成的,我就不認(rèn),我必須要找到它的上一個版本。

          當(dāng)然,如果“上一個版本”也不可見,那就得繼續(xù)往前找。還有,如果是這個事務(wù)自己更新的數(shù)據(jù),它自己還是要認(rèn)的。在實現(xiàn)上, InnoDB為每個事務(wù)構(gòu)造了一個數(shù)組,用來保存這個事務(wù)啟動瞬間,當(dāng)前正在“活躍”的所有事務(wù)ID?!盎钴S”指的就是,啟動了但還沒提交。

          數(shù)組里面事務(wù)ID的最小值記為低水位,當(dāng)前系統(tǒng)里面已經(jīng)創(chuàng)建過的事務(wù)ID的最大值加1記為高水位。

          這個視圖數(shù)組和高水位,就組成了當(dāng)前事務(wù)的一致性視圖(read-view)。而數(shù)據(jù)版本的可見性規(guī)則,就是基于數(shù)據(jù)的row trx_id和這個一致性視圖的對比結(jié)果得到的。

          這個視圖數(shù)組把所有的row trx_id 分成了幾種不同的情況。如下圖所示:

          上圖是數(shù)據(jù)庫版本可見性規(guī)則,對于當(dāng)前事務(wù)的啟動瞬間來說,一個數(shù)據(jù)版本的row trx_id,有以下幾種可能:

          1. 如果落在綠色部分,表示這個版本是已提交的事務(wù)或者是當(dāng)前事務(wù)自己生成的,這個數(shù)據(jù)是可見的;

          2. 如果落在灰色部分,表示這個版本是由將來啟動的事務(wù)生成的,是肯定不可見的;

          3. 如果落在粉色部分,那就包括兩種情況

            • (a) 若 row trx_id在數(shù)組中,表示這個版本是由還沒提交的事務(wù)生成的,不可見;

            • (b) 若 row trx_id不在數(shù)組中,表示這個版本是已經(jīng)提交了的事務(wù)生成的,可見。

          比如,對于圖2中的數(shù)據(jù)來說,如果有一個事務(wù),它的低水位是21,那么當(dāng)它訪問這一行數(shù)據(jù)時,就會從V4通過U3計算出V3,所以在它看來,這一行的值是11。

          你看,有了這個聲明后,系統(tǒng)里面隨后發(fā)生的更新,是不是就跟這個事務(wù)看到的內(nèi)容無關(guān)了呢?因為之后的更新,生成的版本一定屬于上面的2或者3(a)的情況,而對它來說,這些新的數(shù)據(jù)版本是不存在的,所以這個事務(wù)的快照,就是“靜態(tài)”的了。

          所以你現(xiàn)在知道了,InnoDB利用了所有數(shù)據(jù)都有多個版本的這個特性,實現(xiàn)了“秒級創(chuàng)建快照”的能力。

          接下來我們用一個例子來鞏固一下MVCC的知識,例子如下:

          下面是一個只有兩行的表的初始化語句。

          mysql>?CREATE?TABLE?`t`?(
          ??`id`?int(11)?NOT?NULL,
          ??`k`?int(11)?DEFAULT?NULL,
          ??PRIMARY?KEY?(`id`)
          )?ENGINE=InnoDB;
          insert?into?t(id,?k)?values(1,1),(2,2);

          begin/start transaction命令并不是一個事務(wù)的起點,在執(zhí)行到它們之后的第一個操作InnoDB表的語句,事務(wù)才真正啟動。如果你想要馬上啟動一個事務(wù),可以使用start transaction with consistent snapshot這個命令。

          還需要注意的是,我們的例子中如果沒有特別說明,都是默認(rèn)autocommit=1。

          在這個例子中,事務(wù)C沒有顯式地使用begin/commit,表示這個update語句本身就是一個事務(wù),語句完成的時候會自動提交。事務(wù)B在更新了行之后查詢; 事務(wù)A在一個只讀事務(wù)中查詢,并且時間順序上是在事務(wù)B的查詢之后。

          讓我們想一下圖中的三個事務(wù),分析一下事務(wù)A的語句返回的結(jié)果是什么?

          答案:事務(wù)B查到的k的值是3,而事務(wù)A查到的k的值是1,是不是感到有點奇怪?

          接下來我們用假設(shè)分析法,進行如下的假設(shè):

          1. 事務(wù)A開始前,系統(tǒng)里面只有一個活躍事務(wù)ID是99;

          2. 事務(wù)A、B、C的版本號分別是100、101、102,且當(dāng)前系統(tǒng)里只有這四個事務(wù);

          3. 三個事務(wù)開始前,(1,1)這一行數(shù)據(jù)的row trx_id是90。

          這樣,事務(wù)A的視圖數(shù)組就是[99,100], 事務(wù)B的視圖數(shù)組是[99,100,101], 事務(wù)C的視圖數(shù)組是[99,100,101,102]

          為了便于我們分析,接下來我們通過一個圖去分析,如下圖所示:

          這里需要說明一下,start transaction with consistent snapshot;的意思是從這個語句開始,創(chuàng)建一個持續(xù)整個事務(wù)的一致性快照。所以,在讀提交隔離級別下,這個用法就沒意義了,等效于普通的start transaction。

          從圖中可以看到,第一個有效更新是事務(wù)C,把數(shù)據(jù)從(1,1)改成了(1,2)。這時候,這個數(shù)據(jù)的最新版本的row trx_id是102,而90這個版本已經(jīng)成為了歷史版本。

          第二個有效更新是事務(wù)B,把數(shù)據(jù)從(1,2)改成了(1,3)。這時候,這個數(shù)據(jù)的最新版本(即row trx_id)是101,而102又成為了歷史版本。

          你可能注意到了,在事務(wù)A查詢的時候,其實事務(wù)B還沒有提交,但是它生成的(1,3)這個版本已經(jīng)變成當(dāng)前版本了。但這個版本對事務(wù)A必須是不可見的,否則就變成臟讀了。

          好,現(xiàn)在事務(wù)A要來讀數(shù)據(jù)了,它的視圖數(shù)組是[99,100]。當(dāng)然了,讀數(shù)據(jù)都是從當(dāng)前版本讀起的。所以,事務(wù)A查詢語句的讀數(shù)據(jù)流程是這樣的:

          1. 找到(1,3)的時候,判斷出row trx_id=101,比高水位大,處于紅色區(qū)域,不可見;

          2. 接著,找到上一個歷史版本,一看row trx_id=102,比高水位大,處于紅色區(qū)域,不可見;

          3. 再往前找,終于找到了(1,1),它的row trx_id=90,比低水位小,處于綠色區(qū)域,可見。

          這樣執(zhí)行下來,雖然期間這一行數(shù)據(jù)被修改過,但是事務(wù)A不論在什么時候查詢,看到這行數(shù)據(jù)的結(jié)果都是一致的,所以我們稱之為一致性讀。

          這個判斷規(guī)則是我通過一些資料和高性能MYSQL中從代碼邏輯直接轉(zhuǎn)譯過來的,但是正如你所見,用于人肉分析可見性很麻煩。

          一個數(shù)據(jù)版本,對于一個事務(wù)視圖來說,除了自己的更新總是可見以外,有三種情況:

          1. 版本未提交,不可見;

          2. 版本已提交,但是是在視圖創(chuàng)建后提交的,不可見;

          3. 版本已提交,而且是在視圖創(chuàng)建前提交的,可見。

          現(xiàn)在,我們用這個規(guī)則來判斷圖4中的查詢結(jié)果,事務(wù)A的查詢語句的視圖數(shù)組是在事務(wù)A啟動的時候生成的,這時候:

          • (1,3)還沒提交,屬于情況1,不可見;

          • (1,2)雖然提交了,但是是在視圖數(shù)組創(chuàng)建之后提交的,屬于情況2,不可見;

          • (1,1)是在視圖數(shù)組創(chuàng)建之前提交的,可見。

          你看,去掉數(shù)字對比后,只用時間先后順序來判斷,分析起來是不是輕松多了。所以,后面我們就都用這個規(guī)則來分析。

          這時候你是不是有一個這樣的疑問:事務(wù)B的update語句,如果按照一致性讀,好像結(jié)果不對哦?

          事務(wù)B的視圖數(shù)組是先創(chuàng)建的,之后事務(wù)C才提交,不是應(yīng)該看不見(1,2)嗎,怎么能算出(1,3)來?

          確實如此,如果事務(wù)B在更新之前查詢一次數(shù)據(jù),這個查詢返回的k的值確實是1。

          但是,當(dāng)它要去更新數(shù)據(jù)的時候,就不能再在歷史版本上更新了,否則事務(wù)C的更新就丟失了。

          因此,事務(wù)B此時的set k=k+1是在(1,2)的基礎(chǔ)上進行的操作,這里就用到了這樣一條規(guī)則:更新數(shù)據(jù)都是先讀后寫的,而這個讀,只能讀當(dāng)前的值,稱為**當(dāng)前讀。

          因此,在更新的時候,當(dāng)前讀拿到的數(shù)據(jù)是(1,2),更新后生成了新版本的數(shù)據(jù)(1,3),這個新版本的row trx_id是101。所以,在執(zhí)行事務(wù)B查詢語句的時候,一看自己的版本號是101,最新數(shù)據(jù)的版本號也是101,是自己的更新,可以直接使用,所以查詢得到的k的值是3。

          這里我們提到了一個概念,叫作當(dāng)前讀。其實,除了update語句外,select語句如果加鎖,也是當(dāng)前讀。

          因此,如果把事務(wù)A的查詢語句select * from t where id=1修改一下,加上lock in share modefor update,也都可以讀到版本號是101的數(shù)據(jù),返回的k的值是3。下面這兩個select語句,就是分別加了讀鎖(S鎖,共享鎖)和寫鎖(X鎖,排他鎖)。

          mysql>?select?k?from?t?where?id=1?lock?in?share?mode;
          mysql>?select?k?from?t?where?id=1?for?update;

          假設(shè)事務(wù)C不是馬上提交的,而是變成了下面的事務(wù)C’,會怎么樣呢?如下圖所示:

          事務(wù)C’的不同是,更新后并沒有馬上提交,在它提交前,事務(wù)B的更新語句先發(fā)起了。前面說過了,雖然事務(wù)C’還沒提交,但是(1,2)這個版本也已經(jīng)生成了,并且是當(dāng)前的最新版本。那么,事務(wù)B的更新語句會怎么處理呢?

          這時候,我們的兩階段鎖協(xié)議就要上場了。事務(wù)C’沒提交,也就是說(1,2)這個版本上的寫鎖還沒釋放。而事務(wù)B是當(dāng)前讀,必須要讀最新版本,而且必須加鎖,因此就被鎖住了,必須等到事務(wù)C’釋放這個鎖,才能繼續(xù)它的當(dāng)前讀。

          那么回到之前的隔離界別中的事務(wù)的可重復(fù)讀的能力是怎么實現(xiàn)的?

          可重復(fù)讀的核心就是一致性讀(consistent read);而事務(wù)更新數(shù)據(jù)的時候,只能用當(dāng)前讀。如果當(dāng)前的記錄的行鎖被其他事務(wù)占用的話,就需要進入鎖等待。

          而讀提交的邏輯和可重復(fù)讀的邏輯類似,它們最主要的區(qū)別是:

          1. 在可重復(fù)讀隔離級別下,只需要在事務(wù)開始的時候創(chuàng)建一致性視圖,之后事務(wù)里的其他查詢都共用這個一致性視圖;

          2. 在讀提交隔離級別下,每一個語句執(zhí)行前都會重新算出一個新的視圖。

          接下來再看一下,在讀提交隔離級別下,事務(wù)A和事務(wù)B的查詢語句查到的k,分別應(yīng)該是多少呢?如下圖所示:

          可以看到此時事務(wù)A的查詢語句的視圖數(shù)組是在執(zhí)行這個語句的時候創(chuàng)建的,時間線上(1,2)、(1,3)的生成時間都在創(chuàng)建這個視圖數(shù)組的時刻之前。

          但是,在這個時刻:(1,3)還沒提交,屬于情況1,不可見;(1,2)提交了,屬于情況3,可見。所以,這時候事務(wù)A查詢語句返回的是k=2。顯然地,事務(wù)B查詢結(jié)果k=3。

          六、總結(jié)

          本文從底層分析了MySQL的事務(wù)原理,希望對你們有所幫助,最后別忘了點贊喲?。。?/p>

          點個在看支持我吧,轉(zhuǎn)發(fā)就更好了
          瀏覽 36
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  色综合激情| 精品人妻中文字幕 | www.俺来也.com | 大香蕉网伊人在线 | 国产aa|