資料庫ACID、隔離級別與MVCC

OkidoGreen發表於2017-03-31

http://www.cnblogs.com/lidabnu/p/4495785.html

首先需要明確事務的概念:一組原子性的SQL查詢,如果資料庫引擎能夠成功的對資料庫應用該組查詢的全部語句,那麼就執行該組語句,否則所有語句都不執行。

事務有ACID四個特性,即:

原子性:一個事務是一個不可分割的最小工作單元,其操作要麼全部成功,要麼全部失敗;

一致性:資料庫總是從一個一致性狀態轉換為另一個一致性狀態。所謂一致性狀態,就是資料庫的所有完整性約束(尤其注意使用者定義約束)都被遵守,以銀行轉賬為例,“轉賬操作必然導致一個賬戶減少金額,另一個賬戶增加金額,且這兩個賬戶總金額之和不變”就是一個完整性約束。

永續性:一旦事務提交,則其所作的修改就會永久儲存到資料庫中

隔離性:隔離性用於定義事務之間的相互隔離程度,存在四個隔離級別。

首先需要解釋一下幾個跟隔離性相關的概念定義:

(1)髒讀:指事務讀到髒資料,所謂髒資料,指的是不正確的資料,例如事務執行過程中修改了某記錄,然後回滾,如果其他事務讀到了該記錄的中間修改值,則為髒讀。

(2)不可重複讀:事務在執行過程中,多次對同一個已經存在的記錄進行讀取,各次讀取的值不同。讀提交隔離級別存在不可重複讀的問題,事務1、2併發執行,事務2首先讀取記錄1,然後事務1修改記錄1並提交,事務2繼續讀取記錄1,則事務2兩次讀取到的值不同。

(3)幻讀:幻讀是指使用某個條件讀取一批記錄時,可能讀到的記錄數不同。幻讀與髒讀、不可重複讀的區別在於,髒讀、不可重複讀都是針對某個確定的已經存在的記錄出現的值不要求(讀到髒資料或多次讀的值不同),而幻讀則是多次使用同一個條件查詢一批記錄,多次讀到的記錄數不同,也就是說,髒讀、不可重複讀是由於多個事務並行執行update引起的,而幻讀則是由於多個事務並行執行insert引起的(併發delete引起的問題看起來算哪個都行……)。

四個隔離級別為:

(1)Read Uncommited:讀未提交,其含義為多個併發事務,任何一個事務可以讀到其他事務尚未提交的修改:

    存在髒讀、不可重複讀、幻讀可能性。

(2)Read Commited:讀已提交,含義為多個併發事務,任何一個事務只可以讀到其他事務已經提交的修改:

    解決髒讀,存在不可重複讀、幻讀可能性。

(3)Repeatable Read:可重複讀,含義為多個事務併發執行時,任何一個事務反覆讀取已存在的記錄,每次讀到的值都是相同的

    解決髒讀、不可重複讀,存在幻讀可能性。

(4)Serializable:序列化,含義為所有事務序列執行,因此不存在事務併發執行的情況。

    解決髒讀、不可重複讀、幻讀。

 

 

多版本併發控制MVCC

上述四個隔離級別中,讀未提交隔離性最差,且相對於讀已提交,效能並沒有多少提升,幾乎不會使用;序列化隔離性最好,可是效能太差,也幾乎不會使用。一般資料庫的預設隔離級別要麼是讀已提交,要麼是可重複讀(例如MySQL的InnoDB引擎),要麼是讀已提交(例如Oracle )。

如果使用行級讀鎖、寫鎖來實現讀已提交或可重複讀,應當是以下的步驟:

1、事務1會修改行1,則會在行1加上寫鎖,開始事務;

2、事務2為純讀取操作,需要讀取行1,試圖在行1上加上讀鎖,由於事務1已加寫鎖,因此事務2等待直到事務1完成。

3、如果事務2先開始,則事務1也需要等到事務2完成並釋放讀鎖後才可以開始執行。

也即使說,對某行的寫操作會阻塞所有對該行的讀取操作,對某行的讀操作會阻塞所有對該行的寫操作,在系統存在讀、寫併發時,不論系統IO能力有多高,會受限於鎖而導致效能低下。

MVCC用於解決這個問題來提高系統效能,MVCC並沒有統一的標準,各個資料庫實現均採用不同方式來實現MVCC,InnoDB的實現方式如下:

準備工作:

(1)對每行記錄增加行標誌和刪除標誌兩個欄位;

(2)維護一個全域性的系統版本號,每開始一個事務(注意select也是事務,讀事務),將該系統版本號加1並作為事務的版本號

插入記錄的行標誌設定為本事務版本號,刪除標誌為空;

刪除記錄的刪除標誌設定為本事務版本號;

修改的處理過程:將原記錄的刪除版本號修改為本事務版本號;新插入一條記錄,包含原記錄資料及本次修改,行記錄標誌設定為本事務版本號,刪除標誌為空;

讀取的處理過程:

僅讀取同時滿足以下條件的記錄行:

(1)行標誌小於或等於本事務版本號(等於用於保證能夠讀取到本事務內提交的增加);

(2)刪除標誌為空或者大於本事務版本號(不包括等於以保證不會讀取到本事務刪除的記錄);

相當於在讀事務開始的時刻點,建立了一個系統的快照,該事務讀取的所有資料,均是從快照中讀取的,因此滿足可重複讀的條件,並且可解決幻讀的問題,並且也不會讀到產生“同樣查詢條件,事務中第一次讀到的記錄數大於第二次讀到的記錄數的問題“(由併發刪除引起)

從上可知,使用MVCC後,大部分讀都不再需要加讀鎖,因此讀不再阻塞寫,寫也不再阻塞讀。讀操作只再受限於系統IO能力。



昨天去去哪兒網面試,老周和老趙問了很多問題,大多關於細節,其中就包括事務隔離級別和MVCC,由於準備不夠充分,所以今天特地進行驗證。

其中隔離級別中,比較讓人難以理解的是repeatable read可重複讀,和serializable序列讀,下面依次進行試驗,檢視彼此區別。


serializable隔離級別:

session 1 session 2

mysql> show variables like ‘%iso%‘;

+---------------+--------------+

| Variable_name | Value        |

+---------------+--------------+

| tx_isolation  | SERIALIZABLE |

+---------------+--------------+

1 row in set (0.00 sec)

mysql> begin;

Query OK, 0 rows affected (0.00 sec)

mysql> select * from t;

+---+

| i |

+---+

| 1 |

| 2 |

| 3 |

| 4 |

| 5 |

+---+

5 rows in set (0.00 sec)

mysql> show variables like ‘%iso%‘;

+---------------+--------------+

| Variable_name | Value        |

+---------------+--------------+

| tx_isolation  | SERIALIZABLE |

+---------------+--------------+

1 row in set (0.00 sec)

mysql> begin;

Query OK, 0 rows affected (0.00 sec)

mysql> select * from t;

+---+

| i |

+---+

| 1 |

| 2 |

| 3 |

| 4 |

| 5 |

+---+

5 rows in set (0.00 sec)

mysql> insert into t values (6);

ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction



mysql> insert into t values (10);

ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

上面現象說明:當隔離級別為serializable的時候,不相容MVCC,嚴格遵循鎖機制,當session1 和session2都進行全表查詢時,兩個會話都會全表加讀鎖,由於讀鎖只和讀鎖相相容,所以此時任何一個會話都無法修改、插入資料,會進入所等待。


repeatable read隔離級別:


session 1 session 2

mysql> show variables like ‘%iso%‘;

+---------------+-----------------+

| Variable_name | Value           |

+---------------+-----------------+

| tx_isolation  | REPEATABLE-READ |

+---------------+-----------------+

1 row in set (0.00 sec)

mysql> begin;

Query OK, 0 rows affected (0.00 sec)

mysql> select * from t;

+---+

| i |

+---+

| 1 |

| 2 |

| 3 |

| 4 |

| 5 |

+---+

5 rows in set (0.00 sec)

mysql> show variables like ‘%iso%‘;

+---------------+-----------------+

| Variable_name | Value           |

+---------------+-----------------+

| tx_isolation  | REPEATABLE-READ |

+---------------+-----------------+

1 row in set (0.00 sec)

mysql> begin;

Query OK, 0 rows affected (0.00 sec)

mysql> select * from t;

+---+

| i |

+---+

| 1 |

| 2 |

| 3 |

| 4 |

| 5 |

+---+

5 rows in set (0.00 sec)

mysql> insert into t values (6); 

Query OK, 1 row affected (0.00 sec)


mysql> select * from t;

+---+

| i |

+---+

| 1 |

| 2 |

| 3 |

| 4 |

| 5 |

| 6 |

+---+

6 rows in set (0.00 sec)

mysql> select * from t;

+---+

| i |

+---+

| 1 |

| 2 |

| 3 |

| 4 |

| 5 |

+---+

5 rows in set (0.00 sec)


mysql> insert into t values (6); 

ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

上面現象說明:當隔離級別為repeatable read時,相容使用MVCC(使用undo),此時兩個事物讀取資料到各自的undo中,事物之間獨立,但是不同事務對於同一行資料的修改會根據順序加上排他鎖。其中上面session 2最後插入一條資料,是因為session 1已經存在並將該行資料鎖定,同時此刻出現在repeatable read隔離級別所特有的幻讀現象(本會話內並沒有該資料,卻依然無法插入)。


總結:

MVCC:多版本控制,多個未提交事務所看到的資料都是自己的,彼此不同,在客戶端總體看來彷彿多個版本個資料庫。

MVCC只和隔離級別read-committed和repeatable-read相相容,MACC對於不同事物的同一行的讀寫之間是不加鎖的,對於不同事務的同一行的寫寫加鎖。

MVCC和read-uncommitted和serializable不相容,其中serializable完是由鎖來控制,所有事務均符合鎖特徵。


以下內容摘自:http://www.blogjava.net/neverend/archive/2012/04/05/373357.html

在事務隔離級別設定為repeatable read的情況下,一般的select語句採取的是一致性非阻塞讀的方式。
一致性是指在事務的範圍內讀取的資料是可重現的,不會出現不可重複讀的情況。非阻塞是指這種讀取資料的模式不會對資料上任何一種鎖,其它操作全都不會被阻塞。
在這種模式下,事務執行讀取語句後,相關的資料會有一套副本出現,並會為這個資料副本附加一個時間戳,其它事務在這個時間戳之後執行的寫操作都不會反映到這個副本中,這種機制被稱之為多版本併發控制。
如果用select …… lock in share mode,則不是一致性非阻塞讀,該語句會等待其它事務的寫語句提交或回滾之後再讀取資料;如果事務隔離級別設定為read committed,也不是一致性非阻塞讀,該語句會讀取其它事務提交的資料。

相關文章