當(dāng)前位置:首頁(yè) > 公眾號(hào)精選 > 架構(gòu)師社區(qū)
[導(dǎo)讀]“ 今天無(wú)聊來(lái)撩一下MySQL事務(wù),希望你們喜歡~

今天無(wú)聊來(lái)撩一下MySQL事務(wù),希望你們喜歡~

目錄

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

一、概念

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

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

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

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

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

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

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

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

二、隔離性與隔離級(jí)別

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

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

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

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

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

下面我逐一為你解釋?zhuān)?

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

  2. 讀已提交:事務(wù)從開(kāi)始直到提交之前,所做的任何修改對(duì)其他事務(wù)都是不可見(jiàn)的,這個(gè)級(jí)別有時(shí)候也叫做不可重復(fù)讀(Nonrepeatable Read),因?yàn)橥皇聞?wù)中兩次執(zhí)行同樣的查詢(xún),可能會(huì)得到不一樣的結(jié)果

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

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

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

mysql>?create?table?T(c?int)?engine=InnoDB;
insert?into?T(c)?values(1);
面試官靈魂的一擊:你懂MySQL事務(wù)嗎?

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

你一定會(huì)問(wèn),回滾日志總不能一直保留吧,什么時(shí)候刪除呢?

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

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

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

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

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

除了對(duì)回滾段的影響,長(zhǎng)事務(wù)還占用鎖資源,也可能拖垮整個(gè)庫(kù),這個(gè)我們會(huì)在后面講鎖的時(shí)候展開(kāi)。

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

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

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

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

因此,我會(huì)建議你總是使用set autocommit=1, 通過(guò)顯式語(yǔ)句的方式來(lái)啟動(dòng)事務(wù)。

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

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

你可以在information_schema庫(kù)的innodb_trx這個(gè)表中查詢(xún)長(zhǎng)事務(wù),比如下面這個(gè)語(yǔ)句,用于查找持續(xù)時(shí)間超過(guò)60s的事務(wù)。

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

五、MVCC工作原理

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

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

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

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

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

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

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

你可能會(huì)問(wèn),前面的文章不是說(shuō),語(yǔ)句更新會(huì)生成undo log(回滾日志)嗎?那么,undo log在哪呢?

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

明白了多版本和row trx_id的概念后,我們?cè)賮?lái)想一下,InnoDB是怎么定義那個(gè)“100G”的快照的。

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

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

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

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

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

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

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

上圖是數(shù)據(jù)庫(kù)版本可見(jiàn)性規(guī)則,對(duì)于當(dāng)前事務(wù)的啟動(dòng)瞬間來(lái)說(shuō),一個(gè)數(shù)據(jù)版本的row trx_id,有以下幾種可能:

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

  2. 如果落在灰色部分,表示這個(gè)版本是由將來(lái)啟動(dòng)的事務(wù)生成的,是肯定不可見(jiàn)的;

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

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

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

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

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

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

接下來(lái)我們用一個(gè)例子來(lái)鞏固一下MVCC的知識(shí),例子如下:

下面是一個(gè)只有兩行的表的初始化語(yǔ)句。

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);
面試官靈魂的一擊:你懂MySQL事務(wù)嗎?

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

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

在這個(gè)例子中,事務(wù)C沒(méi)有顯式地使用begin/commit,表示這個(gè)update語(yǔ)句本身就是一個(gè)事務(wù),語(yǔ)句完成的時(shí)候會(huì)自動(dòng)提交。事務(wù)B在更新了行之后查詢(xún); 事務(wù)A在一個(gè)只讀事務(wù)中查詢(xún),并且時(shí)間順序上是在事務(wù)B的查詢(xún)之后。

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

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

接下來(lái)我們用假設(shè)分析法,進(jìn)行如下的假設(shè):

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

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

  3. 三個(gè)事務(wù)開(kāi)始前,(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]。

為了便于我們分析,接下來(lái)我們通過(guò)一個(gè)圖去分析,如下圖所示:

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

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

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

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

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

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

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

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

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

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

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

一個(gè)數(shù)據(jù)版本,對(duì)于一個(gè)事務(wù)視圖來(lái)說(shuō),除了自己的更新總是可見(jiàn)以外,有三種情況:

  1. 版本未提交,不可見(jiàn);

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

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

現(xiàn)在,我們用這個(gè)規(guī)則來(lái)判斷圖4中的查詢(xún)結(jié)果,事務(wù)A的查詢(xún)語(yǔ)句的視圖數(shù)組是在事務(wù)A啟動(dòng)的時(shí)候生成的,這時(shí)候:

  • (1,3)還沒(méi)提交,屬于情況1,不可見(jiàn);

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

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

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

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

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

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

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

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

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

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

因此,如果把事務(wù)A的查詢(xún)語(yǔ)句select * from t where id=1修改一下,加上lock in share mode或 for update,也都可以讀到版本號(hào)是101的數(shù)據(jù),返回的k的值是3。下面這兩個(gè)select語(yǔ)句,就是分別加了讀鎖(S鎖,共享鎖)和寫(xiě)鎖(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’,會(huì)怎么樣呢?如下圖所示:

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

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

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

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

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

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

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

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

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

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

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

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

六、總結(jié)

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

免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺(tái)僅提供信息存儲(chǔ)服務(wù)。文章僅代表作者個(gè)人觀點(diǎn),不代表本平臺(tái)立場(chǎng),如有問(wèn)題,請(qǐng)聯(lián)系我們,謝謝!

本站聲明: 本文章由作者或相關(guān)機(jī)構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點(diǎn),本站亦不保證或承諾內(nèi)容真實(shí)性等。需要轉(zhuǎn)載請(qǐng)聯(lián)系該專(zhuān)欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請(qǐng)及時(shí)聯(lián)系本站刪除。
換一批
延伸閱讀

9月2日消息,不造車(chē)的華為或?qū)⒋呱龈蟮莫?dú)角獸公司,隨著阿維塔和賽力斯的入局,華為引望愈發(fā)顯得引人矚目。

關(guān)鍵字: 阿維塔 塞力斯 華為

倫敦2024年8月29日 /美通社/ -- 英國(guó)汽車(chē)技術(shù)公司SODA.Auto推出其旗艦產(chǎn)品SODA V,這是全球首款涵蓋汽車(chē)工程師從創(chuàng)意到認(rèn)證的所有需求的工具,可用于創(chuàng)建軟件定義汽車(chē)。 SODA V工具的開(kāi)發(fā)耗時(shí)1.5...

關(guān)鍵字: 汽車(chē) 人工智能 智能驅(qū)動(dòng) BSP

北京2024年8月28日 /美通社/ -- 越來(lái)越多用戶(hù)希望企業(yè)業(yè)務(wù)能7×24不間斷運(yùn)行,同時(shí)企業(yè)卻面臨越來(lái)越多業(yè)務(wù)中斷的風(fēng)險(xiǎn),如企業(yè)系統(tǒng)復(fù)雜性的增加,頻繁的功能更新和發(fā)布等。如何確保業(yè)務(wù)連續(xù)性,提升韌性,成...

關(guān)鍵字: 亞馬遜 解密 控制平面 BSP

8月30日消息,據(jù)媒體報(bào)道,騰訊和網(wǎng)易近期正在縮減他們對(duì)日本游戲市場(chǎng)的投資。

關(guān)鍵字: 騰訊 編碼器 CPU

8月28日消息,今天上午,2024中國(guó)國(guó)際大數(shù)據(jù)產(chǎn)業(yè)博覽會(huì)開(kāi)幕式在貴陽(yáng)舉行,華為董事、質(zhì)量流程IT總裁陶景文發(fā)表了演講。

關(guān)鍵字: 華為 12nm EDA 半導(dǎo)體

8月28日消息,在2024中國(guó)國(guó)際大數(shù)據(jù)產(chǎn)業(yè)博覽會(huì)上,華為常務(wù)董事、華為云CEO張平安發(fā)表演講稱(chēng),數(shù)字世界的話語(yǔ)權(quán)最終是由生態(tài)的繁榮決定的。

關(guān)鍵字: 華為 12nm 手機(jī) 衛(wèi)星通信

要點(diǎn): 有效應(yīng)對(duì)環(huán)境變化,經(jīng)營(yíng)業(yè)績(jī)穩(wěn)中有升 落實(shí)提質(zhì)增效舉措,毛利潤(rùn)率延續(xù)升勢(shì) 戰(zhàn)略布局成效顯著,戰(zhàn)新業(yè)務(wù)引領(lǐng)增長(zhǎng) 以科技創(chuàng)新為引領(lǐng),提升企業(yè)核心競(jìng)爭(zhēng)力 堅(jiān)持高質(zhì)量發(fā)展策略,塑強(qiáng)核心競(jìng)爭(zhēng)優(yōu)勢(shì)...

關(guān)鍵字: 通信 BSP 電信運(yùn)營(yíng)商 數(shù)字經(jīng)濟(jì)

北京2024年8月27日 /美通社/ -- 8月21日,由中央廣播電視總臺(tái)與中國(guó)電影電視技術(shù)學(xué)會(huì)聯(lián)合牽頭組建的NVI技術(shù)創(chuàng)新聯(lián)盟在BIRTV2024超高清全產(chǎn)業(yè)鏈發(fā)展研討會(huì)上宣布正式成立。 活動(dòng)現(xiàn)場(chǎng) NVI技術(shù)創(chuàng)新聯(lián)...

關(guān)鍵字: VI 傳輸協(xié)議 音頻 BSP

北京2024年8月27日 /美通社/ -- 在8月23日舉辦的2024年長(zhǎng)三角生態(tài)綠色一體化發(fā)展示范區(qū)聯(lián)合招商會(huì)上,軟通動(dòng)力信息技術(shù)(集團(tuán))股份有限公司(以下簡(jiǎn)稱(chēng)"軟通動(dòng)力")與長(zhǎng)三角投資(上海)有限...

關(guān)鍵字: BSP 信息技術(shù)
關(guān)閉
關(guān)閉