自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

一文講清楚MySQL事務(wù)隔離級別和實現(xiàn)原理,開發(fā)人員必備知識點

數(shù)據(jù)庫 MySQL
經(jīng)常提到數(shù)據(jù)庫的事務(wù),那你知道數(shù)據(jù)庫還有事務(wù)隔離的說法嗎,事務(wù)隔離還有隔離級別,那什么是事務(wù)隔離,隔離級別又是什么呢?本文就幫大家梳理一下。

經(jīng)常提到數(shù)據(jù)庫的事務(wù),那你知道數(shù)據(jù)庫還有事務(wù)隔離的說法嗎,事務(wù)隔離還有隔離級別,那什么是事務(wù)隔離,隔離級別又是什么呢?本文就幫大家梳理一下。

MySQL 事務(wù)

本文所說的 MySQL 事務(wù)都是指在 InnoDB 引擎下,MyISAM 引擎是不支持事務(wù)的。

數(shù)據(jù)庫事務(wù)指的是一組數(shù)據(jù)操作,事務(wù)內(nèi)的操作要么就是全部成功,要么就是全部失敗,什么都不做,其實不是沒做,是可能做了一部分但是只要有一步失敗,就要回滾所有操作,有點一不做二不休的意思。

假設(shè)一個網(wǎng)購付款的操作,用戶付款后要涉及到訂單狀態(tài)更新、扣庫存以及其他一系列動作,這就是一個事務(wù),如果一切正常那就相安無事,一旦中間有某個環(huán)節(jié)異常,那整個事務(wù)就要回滾,總不能更新了訂單狀態(tài)但是不扣庫存吧,這問題就大了。

事務(wù)具有原子性(Atomicity)、一致性(Consistency)、隔離性(Isolation)、持久性(Durability)四個特性,簡稱 ACID,缺一不可。今天要說的就是隔離性。

概念說明

以下幾個概念是事務(wù)隔離級別要實際解決的問題,所以需要搞清楚都是什么意思。

臟讀

臟讀指的是讀到了其他事務(wù)未提交的數(shù)據(jù),未提交意味著這些數(shù)據(jù)可能會回滾,也就是可能最終不會存到數(shù)據(jù)庫中,也就是不存在的數(shù)據(jù)。讀到了并一定最終存在的數(shù)據(jù),這就是臟讀。

可重復(fù)讀

可重復(fù)讀指的是在一個事務(wù)內(nèi),最開始讀到的數(shù)據(jù)和事務(wù)結(jié)束前的任意時刻讀到的同一批數(shù)據(jù)都是一致的。通常針對數(shù)據(jù)**更新(UPDATE)**操作。

不可重復(fù)讀

對比可重復(fù)讀,不可重復(fù)讀指的是在同一事務(wù)內(nèi),不同的時刻讀到的同一批數(shù)據(jù)可能是不一樣的,可能會受到其他事務(wù)的影響,比如其他事務(wù)改了這批數(shù)據(jù)并提交了。通常針對數(shù)據(jù)**更新(UPDATE)**操作。

幻讀

幻讀是針對數(shù)據(jù)**插入(INSERT)**操作來說的。假設(shè)事務(wù)A對某些行的內(nèi)容作了更改,但是還未提交,此時事務(wù)B插入了與事務(wù)A更改前的記錄相同的記錄行,并且在事務(wù)A提交之前先提交了,而這時,在事務(wù)A中查詢,會發(fā)現(xiàn)好像剛剛的更改對于某些數(shù)據(jù)未起作用,但其實是事務(wù)B剛插入進來的,讓用戶感覺很魔幻,感覺出現(xiàn)了幻覺,這就叫幻讀。

事務(wù)隔離級別

SQL 標準定義了四種隔離級別,MySQL 全都支持。這四種隔離級別分別是:

  1. 讀未提交(READ UNCOMMITTED)
  2. 讀提交 (READ COMMITTED)
  3. 可重復(fù)讀 (REPEATABLE READ)
  4. 串行化 (SERIALIZABLE)

從上往下,隔離強度逐漸增強,性能逐漸變差。采用哪種隔離級別要根據(jù)系統(tǒng)需求權(quán)衡決定,其中,可重復(fù)讀是 MySQL 的默認級別。

事務(wù)隔離其實就是為了解決上面提到的臟讀、不可重復(fù)讀、幻讀這幾個問題,下面展示了 4 種隔離級別對這三個問題的解決程度。

一文講清楚 MySQL 事務(wù)隔離級別和實現(xiàn)原理,開發(fā)人員必備知識點

 

只有串行化的隔離級別解決了全部這 3 個問題,其他的 3 個隔離級別都有缺陷。

一探究竟

下面,我們來一一分析這 4 種隔離級別到底是怎么個意思。

如何設(shè)置隔離級別

我們可以通過以下語句查看當前數(shù)據(jù)庫的隔離級別,通過下面語句可以看出我使用的 MySQL 的隔離級別是 REPEATABLE-READ,也就是可重復(fù)讀,這也是 MySQL 的默認級別。

  1. # 查看事務(wù)隔離級別 5.7.20 之后 
  2. show variables like 'transaction_isolation'
  3. SELECT @@transaction_isolation 
  4.  
  5. # 5.7.20 之后 
  6. SELECT @@tx_isolation 
  7. show variables like 'tx_isolation' 
  8.  
  9. +---------------+-----------------+ 
  10. | Variable_name | Value           | 
  11. +---------------+-----------------+ 
  12. | tx_isolation  | REPEATABLE-READ | 
  13. +---------------+-----------------+ 

稍后,我們要修改數(shù)據(jù)庫的隔離級別,所以先了解一下具體的修改方式。

修改隔離級別的語句是:set [作用域] transaction isolation level [事務(wù)隔離級別], SET [SESSION | GLOBAL] TRANSACTION ISOLATION LEVEL READ UNCOMMITTED READ COMMITTED | REPEATABLE READ | SERIALIZABLE。

其中作用于可以是 SESSION 或者 GLOBAL,GLOBAL 是全局的,而 SESSION 只針對當前回話窗口。隔離級別是 READ UNCOMMITTED READ COMMITTED | REPEATABLE READ | SERIALIZABLE 這四種,不區(qū)分大小寫。

比如下面這個語句的意思是設(shè)置全局隔離級別為讀提交級別。

  1. mysql> set global transaction isolation level read committed;  

MySQL 中執(zhí)行事務(wù)

事務(wù)的執(zhí)行過程如下,以 begin 或者 start transaction 開始,然后執(zhí)行一系列操作,最后要執(zhí)行 commit 操作,事務(wù)才算結(jié)束。當然,如果進行回滾操作(rollback),事務(wù)也會結(jié)束。

一文講清楚 MySQL 事務(wù)隔離級別和實現(xiàn)原理,開發(fā)人員必備知識點

 

需要注意的是,begin 命令并不代表事務(wù)的開始,事務(wù)開始于 begin 命令之后的第一條語句執(zhí)行的時候。例如下面示例中,select * from xxx 才是事務(wù)的開始,

  1. begin
  2. select * from xxx;  
  3. commit-- 或者 rollback; 

另外,通過以下語句可以查詢當前有多少事務(wù)正在運行。

  1. select * from information_schema.innodb_trx; 

好了,重點來了,開始分析這幾個隔離級別了。

接下來我會用一張表來做一下驗證,表結(jié)構(gòu)簡單如下:

  1. CREATE TABLE `user` ( 
  2.   `id` int(11) NOT NULL AUTO_INCREMENT, 
  3.   `namevarchar(30) DEFAULT NULL
  4.   `age` tinyint(4) DEFAULT NULL
  5.   PRIMARY KEY (`id`) 
  6. ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 

初始只有一條記錄:

  1. mysql> SELECT * FROM user
  2. +----+-----------------+------+ 
  3. | id | name            | age  | 
  4. +----+-----------------+------+ 
  5. |  1 | 古時的風(fēng)箏        |    1 | 
  6. +----+-----------------+------+ 

讀未提交

MySQL 事務(wù)隔離其實是依靠鎖來實現(xiàn)的,加鎖自然會帶來性能的損失。而讀未提交隔離級別是不加鎖的,所以它的性能是最好的,沒有加鎖、解鎖帶來的性能開銷。但有利就有弊,這基本上就相當于裸奔啊,所以它連臟讀的問題都沒辦法解決。

任何事務(wù)對數(shù)據(jù)的修改都會第一時間暴露給其他事務(wù),即使事務(wù)還沒有提交。

下面來做個簡單實驗驗證一下,首先設(shè)置全局隔離級別為讀未提交。

  1. set global transaction isolation level read uncommitted

設(shè)置完成后,只對之后新起的 session 才起作用,對已經(jīng)啟動 session 無效。如果用 shell 客戶端那就要重新連接 MySQL,如果用 Navicat 那就要創(chuàng)建新的查詢窗口。

啟動兩個事務(wù),分別為事務(wù)A和事務(wù)B,在事務(wù)A中使用 update 語句,修改 age 的值為10,初始是1 ,在執(zhí)行完 update 語句之后,在事務(wù)B中查詢 user 表,會看到 age 的值已經(jīng)是 10 了,這時候事務(wù)A還沒有提交,而此時事務(wù)B有可能拿著已經(jīng)修改過的 age=10 去進行其他操作了。在事務(wù)B進行操作的過程中,很有可能事務(wù)A由于某些原因,進行了事務(wù)回滾操作,那其實事務(wù)B得到的就是臟數(shù)據(jù)了,拿著臟數(shù)據(jù)去進行其他的計算,那結(jié)果肯定也是有問題的。

順著時間軸往表示兩事務(wù)中操作的執(zhí)行順序,重點看圖中 age 字段的值。

一文講清楚 MySQL 事務(wù)隔離級別和實現(xiàn)原理,開發(fā)人員必備知識點

 

讀未提交,其實就是可以讀到其他事務(wù)未提交的數(shù)據(jù),但沒有辦法保證你讀到的數(shù)據(jù)最終一定是提交后的數(shù)據(jù),如果中間發(fā)生回滾,那就會出現(xiàn)臟數(shù)據(jù)問題,讀未提交沒辦法解決臟數(shù)據(jù)問題。更別提可重復(fù)讀和幻讀了,想都不要想。

讀提交

既然讀未提交沒辦法解決臟數(shù)據(jù)問題,那么就有了讀提交。讀提交就是一個事務(wù)只能讀到其他事務(wù)已經(jīng)提交過的數(shù)據(jù),也就是其他事務(wù)調(diào)用 commit 命令之后的數(shù)據(jù)。那臟數(shù)據(jù)問題迎刃而解了。

讀提交事務(wù)隔離級別是大多數(shù)流行數(shù)據(jù)庫的默認事務(wù)隔離界別,比如 Oracle,但是不是 MySQL 的默認隔離界別。

我們繼續(xù)來做一下驗證,首先把事務(wù)隔離級別改為讀提交級別。

  1. set global transaction isolation level read committed

之后需要重新打開新的 session 窗口,也就是新的 shell 窗口才可以。

同樣開啟事務(wù)A和事務(wù)B兩個事務(wù),在事務(wù)A中使用 update 語句將 id=1 的記錄行 age 字段改為 10。此時,在事務(wù)B中使用 select 語句進行查詢,我們發(fā)現(xiàn)在事務(wù)A提交之前,事務(wù)B中查詢到的記錄 age 一直是1,直到事務(wù)A提交,此時在事務(wù)B中 select 查詢,發(fā)現(xiàn) age 的值已經(jīng)是 10 了。

這就出現(xiàn)了一個問題,在同一事務(wù)中(本例中的事務(wù)B),事務(wù)的不同時刻同樣的查詢條件,查詢出來的記錄內(nèi)容是不一樣的,事務(wù)A的提交影響了事務(wù)B的查詢結(jié)果,這就是不可重復(fù)讀,也就是讀提交隔離級別。

一文講清楚 MySQL 事務(wù)隔離級別和實現(xiàn)原理,開發(fā)人員必備知識點

 

每個 select 語句都有自己的一份快照,而不是一個事務(wù)一份,所以在不同的時刻,查詢出來的數(shù)據(jù)可能是不一致的。

讀提交解決了臟讀的問題,但是無法做到可重復(fù)讀,也沒辦法解決幻讀。

可重復(fù)讀

可重復(fù)是對比不可重復(fù)而言的,上面說不可重復(fù)讀是指同一事物不同時刻讀到的數(shù)據(jù)值可能不一致。而可重復(fù)讀是指,事務(wù)不會讀到其他事務(wù)對已有數(shù)據(jù)的修改,及時其他事務(wù)已提交,也就是說,事務(wù)開始時讀到的已有數(shù)據(jù)是什么,在事務(wù)提交前的任意時刻,這些數(shù)據(jù)的值都是一樣的。但是,對于其他事務(wù)新插入的數(shù)據(jù)是可以讀到的,這也就引發(fā)了幻讀問題。

同樣的,需改全局隔離級別為可重復(fù)讀級別。

  1. set global transaction isolation level repeatable read

在這個隔離級別下,啟動兩個事務(wù),兩個事務(wù)同時開啟。

首先看一下可重復(fù)讀的效果,事務(wù)A啟動后修改了數(shù)據(jù),并且在事務(wù)B之前提交,事務(wù)B在事務(wù)開始和事務(wù)A提交之后兩個時間節(jié)點都讀取的數(shù)據(jù)相同,已經(jīng)可以看出可重復(fù)讀的效果。

一文講清楚 MySQL 事務(wù)隔離級別和實現(xiàn)原理,開發(fā)人員必備知識點

 

可重復(fù)讀做到了,這只是針對已有行的更改操作有效,但是對于新插入的行記錄,就沒這么幸運了,幻讀就這么產(chǎn)生了。我們看一下這個過程:

事務(wù)A開始后,執(zhí)行 update 操作,將 age = 1 的記錄的 name 改為“風(fēng)箏2號”;

事務(wù)B開始后,在事務(wù)執(zhí)行完 update 后,執(zhí)行 insert 操作,插入記錄 age =1,name = 古時的風(fēng)箏,這和事務(wù)A修改的那條記錄值相同,然后提交。

事務(wù)B提交后,事務(wù)A中執(zhí)行 select,查詢 age=1 的數(shù)據(jù),這時,會發(fā)現(xiàn)多了一行,并且發(fā)現(xiàn)還有一條 name = 古時的風(fēng)箏,age = 1 的記錄,這其實就是事務(wù)B剛剛插入的,這就是幻讀。

一文講清楚 MySQL 事務(wù)隔離級別和實現(xiàn)原理,開發(fā)人員必備知識點

 

要說明的是,當你在 MySQL 中測試幻讀的時候,并不會出現(xiàn)上圖的結(jié)果,幻讀并沒有發(fā)生,MySQL 的可重復(fù)讀隔離級別其實解決了幻讀問題,這會在后面的內(nèi)容說明

串行化

串行化是4種事務(wù)隔離級別中隔離效果最好的,解決了臟讀、可重復(fù)讀、幻讀的問題,但是效果最差,它將事務(wù)的執(zhí)行變?yōu)轫樞驁?zhí)行,與其他三個隔離級別相比,它就相當于單線程,后一個事務(wù)的執(zhí)行必須等待前一個事務(wù)結(jié)束。

MySQL 中是如何實現(xiàn)事務(wù)隔離的

首先說讀未提交,它是性能最好,也可以說它是最野蠻的方式,因為它壓根兒就不加鎖,所以根本談不上什么隔離效果,可以理解為沒有隔離。

再來說串行化。讀的時候加共享鎖,也就是其他事務(wù)可以并發(fā)讀,但是不能寫。寫的時候加排它鎖,其他事務(wù)不能并發(fā)寫也不能并發(fā)讀。

最后說讀提交和可重復(fù)讀。這兩種隔離級別是比較復(fù)雜的,既要允許一定的并發(fā),又想要兼顧的解決問題。

實現(xiàn)可重復(fù)讀

為了解決不可重復(fù)讀,或者為了實現(xiàn)可重復(fù)讀,MySQL 采用了 MVVC (多版本并發(fā)控制) 的方式。

我們在數(shù)據(jù)庫表中看到的一行記錄可能實際上有多個版本,每個版本的記錄除了有數(shù)據(jù)本身外,還要有一個表示版本的字段,記為 row trx_id,而這個字段就是使其產(chǎn)生的事務(wù)的 id,事務(wù) ID 記為 transaction id,它在事務(wù)開始的時候向事務(wù)系統(tǒng)申請,按時間先后順序遞增。

一文講清楚 MySQL 事務(wù)隔離級別和實現(xiàn)原理,開發(fā)人員必備知識點

 

按照上面這張圖理解,一行記錄現(xiàn)在有 3 個版本,每一個版本都記錄這使其產(chǎn)生的事務(wù) ID,比如事務(wù)A的transaction id 是100,那么版本1的row trx_id 就是 100,同理版本2和版本3。

在上面介紹讀提交和可重復(fù)讀的時候都提到了一個詞,叫做快照,學(xué)名叫做一致性視圖,這也是可重復(fù)讀和不可重復(fù)讀的關(guān)鍵,可重復(fù)讀是在事務(wù)開始的時候生成一個當前事務(wù)全局性的快照,而讀提交則是每次執(zhí)行語句的時候都重新生成一次快照。

對于一個快照來說,它能夠讀到那些版本數(shù)據(jù),要遵循以下規(guī)則:

  1. 當前事務(wù)內(nèi)的更新,可以讀到;
  2. 版本未提交,不能讀到;
  3. 版本已提交,但是卻在快照創(chuàng)建后提交的,不能讀到;
  4. 版本已提交,且是在快照創(chuàng)建前提交的,可以讀到;

利用上面的規(guī)則,再返回去套用到讀提交和可重復(fù)讀的那兩張圖上就很清晰了。還是要強調(diào),兩者主要的區(qū)別就是在快照的創(chuàng)建上,可重復(fù)讀僅在事務(wù)開始是創(chuàng)建一次,而讀提交每次執(zhí)行語句的時候都要重新創(chuàng)建一次。

并發(fā)寫問題

存在這的情況,兩個事務(wù),對同一條數(shù)據(jù)做修改。最后結(jié)果應(yīng)該是哪個事務(wù)的結(jié)果呢,肯定要是時間靠后的那個對不對。并且更新之前要先讀數(shù)據(jù),這里所說的讀和上面說到的讀不一樣,更新之前的讀叫做“當前讀”,總是當前版本的數(shù)據(jù),也就是多版本中最新一次提交的那版。

假設(shè)事務(wù)A執(zhí)行 update 操作, update 的時候要對所修改的行加行鎖,這個行鎖會在提交之后才釋放。而在事務(wù)A提交之前,事務(wù)B也想 update 這行數(shù)據(jù),于是申請行鎖,但是由于已經(jīng)被事務(wù)A占有,事務(wù)B是申請不到的,此時,事務(wù)B就會一直處于等待狀態(tài),直到事務(wù)A提交,事務(wù)B才能繼續(xù)執(zhí)行,如果事務(wù)A的時間太長,那么事務(wù)B很有可能出現(xiàn)超時異常。如下圖所示。

一文講清楚 MySQL 事務(wù)隔離級別和實現(xiàn)原理,開發(fā)人員必備知識點

 

加鎖的過程要分有索引和無索引兩種情況,比如下面這條語句

  1. update user set age=11 where id = 1 

id 是這張表的主鍵,是有索引的情況,那么 MySQL 直接就在索引數(shù)中找到了這行數(shù)據(jù),然后干凈利落的加上行鎖就可以了。

而下面這條語句

  1. update user set age=11 where age=10 

表中并沒有為 age 字段設(shè)置索引,所以, MySQL 無法直接定位到這行數(shù)據(jù)。那怎么辦呢,當然也不是加表鎖了。MySQL 會為這張表中所有行加行鎖,沒錯,是所有行。但是呢,在加上行鎖后,MySQL 會進行一遍過濾,發(fā)現(xiàn)不滿足的行就釋放鎖,最終只留下符合條件的行。雖然最終只為符合條件的行加了鎖,但是這一鎖一釋放的過程對性能也是影響極大的。所以,如果是大表的話,建議合理設(shè)計索引,如果真的出現(xiàn)這種情況,那很難保證并發(fā)度。

解決幻讀

上面介紹可重復(fù)讀的時候,那張圖里標示著出現(xiàn)幻讀的地方實際上在 MySQL 中并不會出現(xiàn),MySQL 已經(jīng)在可重復(fù)讀隔離級別下解決了幻讀的問題。

前面剛說了并發(fā)寫問題的解決方式就是行鎖,而解決幻讀用的也是鎖,叫做間隙鎖,MySQL 把行鎖和間隙鎖合并在一起,解決了并發(fā)寫和幻讀的問題,這個鎖叫做 Next-Key鎖。

假設(shè)現(xiàn)在表中有兩條記錄,并且 age 字段已經(jīng)添加了索引,兩條記錄 age 的值分別為 10 和 30。

一文講清楚 MySQL 事務(wù)隔離級別和實現(xiàn)原理,開發(fā)人員必備知識點

 

此時,在數(shù)據(jù)庫中會為索引維護一套B+樹,用來快速定位行記錄。B+索引樹是有序的,所以會把這張表的索引分割成幾個區(qū)間。

一文講清楚 MySQL 事務(wù)隔離級別和實現(xiàn)原理,開發(fā)人員必備知識點

 

如圖所示,分成了3 個區(qū)間,(負無窮,10]、(10,30]、(30,正無窮],在這3個區(qū)間是可以加間隙鎖的。

之后,我用下面的兩個事務(wù)演示一下加鎖過程。

一文講清楚 MySQL 事務(wù)隔離級別和實現(xiàn)原理,開發(fā)人員必備知識點

 

在事務(wù)A提交之前,事務(wù)B的插入操作只能等待,這就是間隙鎖起得作用。當事務(wù)A執(zhí)行update user set name='風(fēng)箏2號’ where age = 10; 的時候,由于條件 where age = 10 ,數(shù)據(jù)庫不僅在 age =10 的行上添加了行鎖,而且在這條記錄的兩邊,也就是(負無窮,10]、(10,30]這兩個區(qū)間加了間隙鎖,從而導(dǎo)致事務(wù)B插入操作無法完成,只能等待事務(wù)A提交。不僅插入 age = 10 的記錄需要等待事務(wù)A提交,age<10、10<age<30 的記錄頁無法完成,而大于等于30的記錄則不受影響,這足以解決幻讀問題了。

這是有索引的情況,如果 age 不是索引列,那么數(shù)據(jù)庫會為整個表加上間隙鎖。所以,如果是沒有索引的話,不管 age 是否大于等于30,都要等待事務(wù)A提交才可以成功插入。

總結(jié)

MySQL 的 InnoDB 引擎才支持事務(wù),其中可重復(fù)讀是默認的隔離級別。

讀未提交和串行化基本上是不需要考慮的隔離級別,前者不加鎖限制,后者相當于單線程執(zhí)行,效率太差。

讀提交解決了臟讀問題,行鎖解決了并發(fā)更新的問題。并且 MySQL 在可重復(fù)讀級別解決了幻讀問題,是通過行鎖和間隙鎖的組合 Next-Key 鎖實現(xiàn)的。 

 

責任編輯:龐桂玉 來源: 今日頭條
相關(guān)推薦

2021-10-19 10:10:51

MySQL事務(wù)隔離級別數(shù)據(jù)庫

2021-10-29 11:30:31

補碼二進制反碼

2018-05-21 07:08:18

行為驅(qū)動開發(fā)BDD編碼

2024-02-23 10:41:29

2012-06-28 09:56:36

設(shè)計交互設(shè)計

2019-02-01 10:56:04

2012-06-28 10:48:31

設(shè)計交互設(shè)計

2024-01-05 07:55:39

Linux虛擬內(nèi)存

2021-04-21 10:00:08

MySQL索引數(shù)據(jù)庫

2009-04-03 10:00:56

2020-04-07 09:21:45

MySQL數(shù)據(jù)庫SQL

2009-06-22 09:13:55

測試開發(fā)人員

2020-02-21 20:10:13

搞懂事務(wù)隔離級別

2023-02-06 18:27:00

開發(fā)人員語言

2021-07-07 10:28:09

分布式架構(gòu)系統(tǒng)

2022-07-15 15:22:51

區(qū)塊鏈開發(fā)語言

2018-11-21 12:21:33

Ruby框架Web應(yīng)用

2020-10-14 09:45:29

Web開發(fā)瀏覽器

2019-07-07 08:18:10

MySQL索引數(shù)據(jù)庫

2020-06-19 16:25:19

MySQL日志文件數(shù)據(jù)庫
點贊
收藏

51CTO技術(shù)棧公眾號