事務(wù)隔離級別

引言

事務(wù)是一組要么全部執(zhí)行,要么全部不執(zhí)行的語句(滿足原子性)。事務(wù)在定義上必須要滿足ACID四大要素,即Atomicity原子性、Consistency一致性、Isolation隔離性、Durability持久性MysqlInoodb存儲引擎是支持事務(wù)的,其滿足ACID四大特性。其中,除了隔離性以外的三大特性均由redo logundo log來保證,而隔離性由鎖來提供。鎖機制可以保證最高程度上的隔離性, 例如串行的依次執(zhí)行多個事務(wù),從而保證在并發(fā)修改下的正確性。但事實上這樣的開銷太大了。為了在正確性和效率之間折衷,SQL標(biāo)準(zhǔn),定義了四種隔離級別供使用者選擇。

隔離級別 臟讀 不可重復(fù)讀 幻讀
READ UNCOMMITED 可能發(fā)生 可能發(fā)生 可能發(fā)生
READ COMMITED 不可能發(fā)生 可能發(fā)生 可能發(fā)生
REPEATABLE READ 不可能發(fā)生 不可能發(fā)生 可能發(fā)生
SERIALIZABLE 不可能發(fā)生 不可能發(fā)生 不可能發(fā)生

四種隔離級別從上到下依次提高,隔離級別越低,事務(wù)所持有的鎖越少,或時間越短。此外,以上的四個隔離級別只是SQL定義的標(biāo)準(zhǔn),提供參考。具體的存儲引擎在實現(xiàn)時往往有自己的一套標(biāo)準(zhǔn),比如Inoodb默認(rèn)情況下是REPEATABLE READ的,但實際上得益于間隙鎖,在這個級別下Inoodb也杜絕了幻讀現(xiàn)象的發(fā)生。下面通過舉例的方式來解釋什么是臟讀、不可重讀、幻讀。在此之前先建一張示例表,其中id是INT類型的主鍵。

 CREATE TABLE tb1(
     id INT,
     name VARCHAR(100),
     PRIMARY KEY(id)
 )Engine=InnoDB CHARSET=utf8;

在這張表里插入一行演示數(shù)據(jù)

INSERT INTO tb1 VALUES(1, 'Tom');
+----+------+
| id | name |
+----+------+
|  1 | Tom  |
+----+------+
臟讀

只有READ UNCOMMITED隔離級別下會發(fā)生,如圖READ UNCOMMITED這個名字一樣,臟讀就是讀到了(其他事務(wù))修改但未提交(uncommited)的臟數(shù)據(jù)。例如兩者同時進(jìn)行的事務(wù)A和事務(wù)B,事務(wù)B對某個記錄執(zhí)行的修改操作,再沒有提交之前就可以被事務(wù)A察覺到,那么此時就發(fā)生了臟讀現(xiàn)象,其時序圖如下。

發(fā)生臟讀

在實驗之前我們首先要在每個事物開始之前修改當(dāng)前會話的隔離級別為READ UNCOMMITED。

SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
SELECT @@tx_isolation
+------------------+
| @@tx_isolation   |
+------------------+
| READ-UNCOMMITTED |
+------------------+

然后依照上面的執(zhí)行流程執(zhí)行SQL語句,可以發(fā)現(xiàn)在事務(wù)A第一次SELECT的時候,此時主鍵id=1數(shù)據(jù)還未被更改,依然為Tom

SELECT * FROM tb1 WHERE id=1;
+----+------+
| id | name |
+----+------+
|  1 | Tom  |
+----+------+

當(dāng)?shù)诙螆?zhí)行SELECT查詢時,此時數(shù)據(jù)卻變?yōu)?code>Ben,且事務(wù)B執(zhí)行更新操作后還未提交。

SELECT * FROM tb1  WHERE id = 1;
+----+------+
| id | name |
+----+------+
|  1 | Ben  |
+----+------+

此時發(fā)生了臟讀現(xiàn)象。

不可重復(fù)讀

不可重復(fù)讀,是指在一個事務(wù)中,先后兩次對同一條記錄的讀取結(jié)果出現(xiàn)不一致的情況,也就是說該記錄被其他事務(wù)修改過了,注意這里要和臟讀區(qū)分開,在臟讀里,事務(wù)B即使未提交(uncommited),也可以影響到事務(wù)A的查詢結(jié)果,而在不可重復(fù)讀條件下,未提交的事務(wù)B是不會影響到事務(wù)A的查詢結(jié)果的(未發(fā)生臟讀),只有當(dāng)是事務(wù)B提交后,才會影響到事務(wù)A的查詢結(jié)果,下圖給出發(fā)生不可重復(fù)讀現(xiàn)象的執(zhí)行時序圖,圖中黃色部分重點強調(diào)了commit的時間點,用以與臟讀現(xiàn)象區(qū)分。

不可重復(fù)讀

依照這個時序圖來依次執(zhí)行sql語句,首先,讓我們把隔離級別改成READ COMMITED,這個級別下臟讀不會發(fā)生,但會出現(xiàn)不可重復(fù)讀現(xiàn)象。

SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
SELECT @@tx_isolation;
+----------------+
| @@tx_isolation |
+----------------+
| READ-COMMITTED |
+----------------+

先看下目前tb1表中的記錄

SELECT * FROM tb1;
+----+------+
| id | name |
+----+------+
|  1 | Tom  |
+----+------+

事務(wù)A開始事務(wù),執(zhí)行第一次查詢語句,然后等待

# 事務(wù)A
BEGIN;
SELECT * FROM tb1 WHERE id = 1;
+----+------+
| id | name |
+----+------+
|  1 | Tom  |
+----+------+

事務(wù)B開始事務(wù),更新id=1的記錄name字段為ben,然后提交

# 事務(wù)B
BEGIN;
UPDATE tb1 SET name = 'Ben' WHERE id = 1;
COMMIT;

再回到事務(wù)A,再執(zhí)行第二次查詢,發(fā)現(xiàn)記錄已經(jīng)被已提交的事務(wù)B修改了,發(fā)生了不可重復(fù)讀現(xiàn)象。

# 事務(wù)A
 SELECT * FROM tb1 WHERE id = 1;
+----+------+
| id | name |
+----+------+
|  1 | Ben  |
+----+------+
幻讀

幻讀是說,在一個事務(wù)中,先后兩次范圍查詢,由于另一個事務(wù)的插入操作,導(dǎo)致第二次查詢結(jié)果的記錄數(shù)比第一次多,憑空多出了一些記錄的現(xiàn)象。其執(zhí)行時序圖如下。


幻讀

注意接下來的實驗中,不能將隔離級別設(shè)置為REPEATABLE READ,因為之前說過,在Innodb中使用了間隙鎖,在這個REPEATABLE READ級別也不會發(fā)生幻讀,所以這里還是用READ-COMMITTED隔離級別做演示。

首先開始事務(wù)A,進(jìn)行第一次查詢所有主鍵值id>=1的記錄,然后等待。

# 事務(wù)A
BEGIN;
SELECT * FROM tb1 WHERE id >= 1;
+----+------+
| id | name |
+----+------+
|  1 | Tom  |
+----+------+

開始執(zhí)行事務(wù)B,插入id=2 name='Ben'的記錄,并提交

BEGIN;
INSERT INTO tb1 VALUES(2, 'Ben');
COMMIT;

回到事務(wù)A,再次查詢,發(fā)現(xiàn)多出了上次查詢中不存在的記錄(2, 'Ben'),發(fā)生了幻讀現(xiàn)象

SELECT * FROM tb1 WHERE id >= 1;
+----+------+
| id | name |
+----+------+
|  1 | Tom  |
|  2 | BEN  |
+----+------+
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容