1. 程式人生 > 實用技巧 >【MySQL】搞懂ACID原則和事務隔離級別

【MySQL】搞懂ACID原則和事務隔離級別

宜未雨而綢繆,毋臨渴而掘井

什麼是事務

資料庫事務(Database Transaction) ,是指作為單個邏輯工作單元執行的一系列操作,要麼全部執行,要麼全部不執行。

一個數據庫事務通常包含對資料庫進行讀或寫的一個操作序列。它的存在包含有以下兩個目的:

  • 為資料庫提供一個從失敗恢復正常狀態的方法,同時提供了資料庫即使在異常狀態下仍然能保持一致性的方法。
  • 當多個應用程式併發訪問資料庫時,可以在這些應用程式之間提供一個隔離的方法,以防止彼此的操作相互干擾。

當然並不是所有的資料庫都支援事務,確切說應該是儲存引擎。在InnoDB儲存引擎中,事務一般有四個屬性:原子性、一致性、隔離性、永續性。簡稱:ACID原則。接下來我們舉例逐個分析:

  • 原子性:顧名思義原子就不可在分的,事務作為一個整體,對資料庫的執行要麼成功,要麼失敗。
  • 一致性:事務從一個一致狀態轉換到另一個一致狀態。
  • 隔離性:多個事務併發執行,事務之間的執行互不干擾。
  • 永續性:一旦一個事務提交,它對資料庫的操作將永久儲存到資料庫當中。

舉例:

我們用一個轉賬的案例結合每個性質分析,例如:賬戶A向賬戶B轉賬,主要分為一下幾個步驟:

  1. 從A賬號中把餘額讀出來(500)。
  2. 對A賬號做減法操作(500-100)。
  3. 把結果寫回A賬號中(400)。
  4. 從B賬號中把餘額讀出來(500)。
  5. 對B賬號做加法操作(500+100)。
  6. 把結果寫回B賬號中(600)。

原子性

保證6個步驟要麼全部執行,要麼全部不執行,如果失敗了就把事務回滾到轉賬的初始狀態。比如:在執行到第五步的之後,賬戶B突然登出了找不到了,此時賬戶A的錢也扣了,就必須事務回滾到原來各自的狀態也就是A的餘額500。

一致性

在轉賬之前A和B的賬戶共有500+500=1000,而轉賬成功之後,A和B的賬戶是400+600=1000,就是資料的狀態在執行該事務操作之後從一個狀態改變到了另外一個狀態

隔離性

在A給B轉賬過程中,只要事務沒有提交,A和B的賬戶餘額不會存在變化。但是此時賬戶C也在向賬戶B轉賬,最終兩個事務提交之後賬戶B的餘額應該是賬戶A轉賬的金額加上賬戶C轉賬的金額。也就是說多個事務之間不會相互影響,至於C給B轉賬的時候獲取B的餘額是已經加了A給B轉賬的餘額還是沒加,這個和事務的隔離級別有關係。

永續性

一旦轉賬成功(事務提交),兩個賬戶的裡面的錢就會真的發生變化(會把資料寫入資料庫做持久化儲存)!

事務的隔離級別

  • Read-uncommitted(讀未提交):最低級別,以上情況均無法保證。
  • Read-committed(讀已提交):可避免髒讀情況發生
  • Repeatable-read(可重複讀,預設):可避免髒讀、不可重複讀情況的發生不可以避免虛讀
  • Serializable:可避免髒讀、不可重複讀、虛讀情況的發生。(序列,不僅有read、write鎖還有range lock範圍鎖(沒有where鎖全表,有where鎖where範圍);對一張表的所有增刪改操作必須順序執行,效能最差)
隔離級別 髒讀 不可重複讀 幻讀
Read-uncommitted
Read-committed ×
Repeatable-read × ×
Serializable × × ×

Read uncommitted

作用:所有事務都可以看到其他未提交事務的執行結果

例子:

又到月底了,小明的老婆要準備給小明發生活費了,小明的老婆給小明打了500塊,但該事務並沒有提交,而此時小明正好在查餘額,發現是550塊,高興的差點蹦了起來.天有不測風雲,突然小明的老婆發現多打了50塊,於是回滾事務,修改金額,然後將事務提交,最後小明空歡喜一場。這個過程中小明讀取到了老婆未提交的金額550,產生了髒讀。

Read committed

作用:一個事務只能看見已經提交事務所做的改變

例子:

某個夜黑風高的夜晚,小明豐富的夜生活開始了,小明拿著工資卡去消費,pos機讀取卡的資訊的時候有500,而此時小紅也正好在網上轉賬,把小明工資卡的500元轉到另一賬戶,並小明之前提交了事務,當小明釦款時,系統檢查到小明的工資卡已經沒有錢,扣款失敗,小明十分納悶,明明卡里有錢,為什麼會說餘額不足,出現上述情況,即我們所說的不可重複讀,兩個併發的事務,“事務1:小紅網上轉賬”、“事務2:小明消費”,事務1事先讀取了資料,緊接了更新了資料,然後事務2查詢資料,之後事務1優先提交了事務,而事務2再次讀取該資料時,資料已經發生了改變,當隔離級別設定為Read committed時,避免了髒讀,但是可能會造成不可重複讀

Repeatable read

  • 備註
    1. InnoDB儲存引擎的預設隔離級別
    2. 從原理上看,可重複讀是靠MVCC(多版本併發控制)保證的,該模式下,保證事務只能讀取到當前事務開啟之前已經提交的事務的修改以及當前事務本身對資料的修改
    3. 可重複讀隔離級別無法避免幻讀

由於幻讀的案例比較特殊,現在暫時沒法通過一個例子畫圖表示,具體過程可以檢視後面的會話比較。

  • Serializable(序列)

作用:最高級別,防止上述3種情況,事務序列執行,慎用這是最高的隔離級別,它通過強制事務排序,使之不可能相互衝突,從而避免了髒讀,不可重複讀,幻讀。簡言之,它是在每個讀的資料行上加上共享鎖。在這個級別,可能導致大量的超時現象和鎖競爭,併發效能最差,在分散式事務中可能會被用到。

測試案例

  • 環境準備
CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `NAME` varchar(20),
  PRIMARY KEY (`id`)
) ENGINE=InnoDB;

insert into user values(1,'zhangsan');
insert into user values(2,'lisi');
insert into user values(3,'wangwu');

mysql> select * from user;
+----+----------+
| id | NAME     |
+----+----------+
|  1 | zhangsan |
|  2 | lisi     |
|  3 | wangwu   |
+----+----------+
3 rows in set (0.00 sec)

--檢視當前事務隔離級別
mysql> SELECT @@tx_isolation;
+-----------------+
| @@tx_isolation  |
+-----------------+
| REPEATABLE-READ |
+-----------------+
1 row in set, 1 warning (0.01 sec)

髒讀案例

測試條件:

  • InnoDB儲存引擎
  • 兩個會話客戶端
  • Read-uncommitted隔離級別
Session-1 Session-2

髒讀:Session-1事務中未提交前修改name欄位值,在Session-2事務未提交前可以檢視到

不可重複讀案例

測試條件:

  • InnoDB儲存引擎
  • 兩個會話客戶端
  • Read-committed隔離級別
Session-1 Session-2

不可重複讀:Session-1事務中未提交的資料修改時,Session-2事務未提交前無法檢視(避免了髒讀),當Session-1事務提交後,Session-2事務在未提交前,檢視到Session-1提交的修改。(造成了不可重複讀,一個事務執行過程中兩次讀取的資料不一樣)

幻讀案例

測試條件:

  • InnoDB儲存引擎
  • 兩個會話客戶端
  • Repaetable read隔離級別
Session-1 Session-2

幻讀:在Repeatable read隔離級別下,Session-1無論在事務提交前還是提交後,在Session-2事務中始終保持的都是開始事務之前的資料狀態,從而避免了髒讀,不可重複讀,但是當Session-1進行插入一條資料時,Session-2同樣無法看到,但是此時Session-2同樣也插入一條資料,就會處於阻塞狀態。感覺就像是,明明沒有資料,為什麼不能插入呢,從而造成了幻讀。當然對於刪除也是同樣的,可能會出現刪除記錄不存在。

不過,InnoDB預設是行鎖,也就是鎖只會鎖住當前的一行,如果Session-2執行insert into user values(5,'xxx')就可以成功執行。

參考文獻:
http://www.hollischuang.com/archives/898