溫馨提示×

溫馨提示×

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

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

MySQL中的事務(wù)隔離是什么意思

發(fā)布時間:2021-08-30 10:39:54 來源:億速云 閱讀:160 作者:chen 欄目:MySQL數(shù)據(jù)庫

本篇內(nèi)容介紹了“MySQL中的事務(wù)隔離是什么意思”的有關(guān)知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細閱讀,能夠?qū)W有所成!

事務(wù)就是要保證一組數(shù)據(jù)庫操作,要么全部成功,要么全部失敗。在MySQL中,事務(wù)支持是在引擎層實現(xiàn)的,但并不是所有的引擎都支持事務(wù)。比如MySQL原生的MyISAM引擎就不支持事務(wù)。

一、事務(wù)的特性

  • 原子性:一個事務(wù)中的所有操作,要么全部完成,要么全部不完成,不會結(jié)束在中間某個環(huán)節(jié)。事務(wù)在執(zhí)行過程中發(fā)生錯誤,會被回滾到事務(wù)開始前的狀態(tài),就像這個事務(wù)從來沒有執(zhí)行過一樣

  • 一致性:在事務(wù)開始之前和事務(wù)結(jié)束以后,數(shù)據(jù)庫的完整性沒有被破壞

  • 隔離性:數(shù)據(jù)庫允許多個并發(fā)事務(wù)同時對數(shù)據(jù)進行讀寫和修改的能力,隔離性可以防止多個事務(wù)并發(fā)執(zhí)行時由于交叉執(zhí)行而導(dǎo)致數(shù)據(jù)的不一致

  • 持久性:事務(wù)處理結(jié)束后,對數(shù)據(jù)的修改就是永久的,即便系統(tǒng)故障也不會丟失

二、隔離級別

1.當(dāng)數(shù)據(jù)庫上有多個事務(wù)同時執(zhí)行的時候,就可能出現(xiàn)臟讀、不可重復(fù)讀、幻讀的問題

  • 臟讀:B事務(wù)讀取到了A事務(wù)尚未提交的數(shù)據(jù)

  • 不可重復(fù)讀:一個事務(wù)讀取到了另一個事務(wù)中提交的update的數(shù)據(jù)

  • 幻讀/虛讀:一個事務(wù)讀取到了另一個事務(wù)中提交的insert的數(shù)據(jù)

2.事務(wù)的隔離級別包括:讀未提交、讀提交、可重復(fù)讀和串行化

  • 讀未提交:一個事務(wù)還沒提交時,它做的變更就能被別的事務(wù)看到

  • 讀提交:一個事務(wù)提交之后,它做的變更才會被其他事務(wù)看到(解決臟讀,Oracle默認的隔離級別)

  • 可重復(fù)讀:一個事務(wù)執(zhí)行過程中看到的數(shù)據(jù),總是跟這個事務(wù)在啟動時看到的數(shù)據(jù)是一致的,而且未提交變更對其他事務(wù)也是不可見的(解決臟讀和不可重復(fù)讀,MySQL默認的隔離級別)

  • 串行化:對于同一行記錄,寫會加寫鎖,讀會加讀鎖,當(dāng)出現(xiàn)讀寫鎖沖突的時候,后訪問的事務(wù)必須等前一個事務(wù)執(zhí)行完成,才能繼續(xù)執(zhí)行(解決臟讀、不可重復(fù)讀和幻讀)

安全性依次提交,性能依次降低

3.假設(shè)數(shù)據(jù)表T中只有一列,其中一行的值為1

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

下面是按照時間順序執(zhí)行兩個事務(wù)的行為:

MySQL中的事務(wù)隔離是什么意思

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

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

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

  • 若隔離級別是串行化,V1、V2值是1,V3是2

在實現(xiàn)上,數(shù)據(jù)庫里面會創(chuàng)建一個視圖,訪問的時候以視圖的邏輯結(jié)果為準。在可重復(fù)讀隔離級別下,這個視圖是在事務(wù)啟動時創(chuàng)建的,整個事務(wù)存在期間都用這個視圖。在讀提交隔離級別下,這個視圖是在每個SQL語句開始執(zhí)行的時候創(chuàng)建的。讀未提交隔離級別下直接返回記錄上的最新值,沒有視圖概念;而串行化隔離級別下直接用加鎖的方式來避免并行訪問

三、事務(wù)隔離的實現(xiàn)(以可重復(fù)讀為例)

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

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

MySQL中的事務(wù)隔離是什么意思

當(dāng)前值是4,但是在查詢這條記錄的時候,不同時刻啟動的事務(wù)會有不同的read-view。如圖中看到的,在視圖A、B、C里面,這一個記錄的值分別是1、2、4,同一條記錄在系統(tǒng)中可以存在多個版本,就是數(shù)據(jù)庫的多版本并發(fā)控制(MVCC)。對于read-viewA,要得到1,就必須將當(dāng)前值一次執(zhí)行圖中所有的回滾操作得到

即使現(xiàn)在有另外一個事務(wù)正在將4改成5,這個事務(wù)跟read-view A、B、C對應(yīng)的事務(wù)是不會沖突的

系統(tǒng)會判斷,當(dāng)沒有事務(wù)再需要用到這些回滾日志時,回滾日志會被刪除

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

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

  • 顯示啟動事務(wù)語句,begin或start transaction。提交語句是commit,回滾語句是rollback

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

建議使用set autocommit=1,通過顯示語句的方式來啟動事務(wù)

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

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

五、事務(wù)隔離還是不隔離

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

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);

事務(wù)A、B、C的執(zhí)行流程如下,采用可重復(fù)讀隔離級別
MySQL中的事務(wù)隔離是什么意思

begin/start transaction命令:不是一個事務(wù)的起點,在執(zhí)行到它們之后的第一個操作InnoDB表的語句,事務(wù)才真正啟動,一致性視圖是在執(zhí)行第一個快照讀語句時創(chuàng)建的

start transaction with consistent snapshot命令:馬上啟動一個事務(wù),一致性視圖是在執(zhí)行這條命令時創(chuàng)建的

按照上圖的流程執(zhí)行,事務(wù)B查到的k的值是3,而事務(wù)A查到的k的值是1

1、快照在MVCC里是怎么工作的?

在可重復(fù)讀隔離級別下,事務(wù)啟動的時候拍了個快照。這個快照是基于整個庫的,那么這個快照是如何實現(xiàn)的?

InnoDB里面每個事務(wù)有一個唯一的事務(wù)ID,叫做transaction id。它在事務(wù)開始的時候向InnoDB的事務(wù)系統(tǒng)申請,是按申請順序嚴格遞增的

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

下圖是一個記錄被多個事務(wù)連續(xù)更新后的狀態(tài):

MySQL中的事務(wù)隔離是什么意思

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

按照可重復(fù)讀的定義,一個事務(wù)啟動的時候,能夠看到所以已經(jīng)提交的事務(wù)結(jié)果。但是之后,這個事務(wù)執(zhí)行期間,其他事務(wù)的更新對它不可見。在實現(xiàn)上,InnoDB為每個事務(wù)構(gòu)造了一個數(shù)組,用來保存這個事務(wù)啟動瞬間,當(dāng)前在啟動了但還沒提交的所有事務(wù)ID。數(shù)組里面事務(wù)ID的最小值記為低水位,當(dāng)前系統(tǒng)里面已經(jīng)創(chuàng)建過的事務(wù)ID的最大值加1記為高水位。這個視圖數(shù)組和高水位就組成了當(dāng)前事務(wù)的一致性視圖。而數(shù)據(jù)的可見性規(guī)則,就是基于數(shù)據(jù)的row trx_id和這個一致性視圖的對比結(jié)果得到的

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

MySQL中的事務(wù)隔離是什么意思

對于當(dāng)前事務(wù)的啟動瞬間來說,一個數(shù)據(jù)版本的row trx_id,有以下幾種可能:

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

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

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

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

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

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

2、為什么事務(wù)A的查詢語句返回的結(jié)果是k=1?

假設(shè):

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

2.事務(wù)A、B、C的版本號分別是100、101、102

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]

MySQL中的事務(wù)隔離是什么意思

從上圖中可以看到,第一個有效更新是事務(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ù)的最新版本是101,而102又成為了歷史版本

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

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

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

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

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

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

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

  • 版本未提交,不可見

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

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

事務(wù)A的查詢語句的視圖數(shù)組是在事務(wù)A啟動的時候生成的,這時候:

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

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

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

3、為什么事務(wù)B的查詢語句返回的結(jié)果是k=3?

MySQL中的事務(wù)隔離是什么意思

事務(wù)B要去更新數(shù)據(jù)的時候,就不能再在歷史版本上更新了,否則事務(wù)C的更新就丟失了。因此,事務(wù)B此時的set k=k+1是在(1,2)的基礎(chǔ)上進行的操作

更新數(shù)據(jù)都是先讀后寫的,而這個讀,只能讀當(dāng)前的值,稱為當(dāng)前讀。除了update語句外,select語句如果加鎖,也是當(dāng)前讀

假設(shè)事務(wù)C不是馬上提交的,而是變成了下面的事務(wù)C’,會怎么樣?
MySQL中的事務(wù)隔離是什么意思
上圖中,事務(wù)C更新后沒有馬上提交,在它提交前,事務(wù)B的更新語句先發(fā)起了。雖然事務(wù)C還沒提交,但是(1,2)這個版本也已經(jīng)生成了,并且是當(dāng)前的最新版本

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

MySQL中的事務(wù)隔離是什么意思

七、事務(wù)的可重復(fù)讀的能力是怎么實現(xiàn)的?

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

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

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

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

“MySQL中的事務(wù)隔離是什么意思”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實用文章!

向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