MySQL事務隔離小記
大家都知道事務系統有四大特徵:原子性、一致性、隔離性、永續性。隔離性是其中重要的一環,什麼是隔離呢,顧名思義就是事務之間啥時候可見啥時候不可見,這就是MySQL的四個隔離級別:
- 未提交讀(read uncommited)
- 提交讀(read commited)
- 可重複讀(repeatable read)
- 序列讀(serializable)
其實前兩種從名字上就能理解什麼意思,未提交讀是事務沒提交呢,別的事務就讀到了,也就是可以讀取事務的中間狀態,即常說的髒讀,這違反了事務的原子性和一致性;提交讀呢,只有事務提交了,其他事務才可以讀取,提交讀解決了髒讀問題卻存在如下問題,比如A事務和B事務並行執行,假設A事務第一次讀取了欄位name是“小明”,這個時候B事務修改了name為“小紅”,接下來A事務又讀取了這個欄位,發現“小明”變成了“小紅”,“小明”去哪了,說好的隔離呢,這種問題被稱為不可重複讀,所以有時候提交讀也稱為不可重複讀。
可重複讀就是為解決不可重複讀問題而出現的另一個隔離級別,也是MySQL的預設事務隔離級別。但是可重複讀也不是完美無缺的,比如A事務和B事務同時執行,A先查詢name欄位為“小紅”的記錄發現沒有,這時候B新增name為“小紅”的記錄,A又執行一次查詢,發現有“小紅”這條記錄了,即所謂的幻行,A像產生了幻覺一樣,這種問題被稱為幻讀。
序列讀就是所有的事務序列化執行,看似完美解決了所有的問題,卻付出了加鎖同步的代價。
總結四種級別的問題矩陣:
隔離級別 | 髒讀 | 不可重複讀 | 幻度 | 加鎖讀 |
---|---|---|---|---|
未提交讀 | 是 | 是 | 是 | 否 |
提交讀 | 否 | 是 | 是 | 否 |
可重複讀 | 否 | 否 | 是 | 否 |
序列讀 | 否 | 否 | 否 | 是 |
綜合來看,第一種隔離級別太低違反了原子性和一致性,最後一種序列讀效率太低在實際專案中鮮見使用,第二第三種都有一個幻讀的問題,接下來看看MySQL如何解決這個問題。
MySQL使用了一種稱之為多版本併發控制(MVCC)的機制,通過在每行記錄後面儲存兩個隱藏列,一個儲存了行的建立時間,一個儲存了行的刪除時間,這裡的“時間”實際上是版本號,說到版本號你可能會猜測這應該是一種類似於樂觀鎖的併發控制機制,沒錯,MySQL就是通過這兩個列實現了一種樂觀鎖。每當開始一個事務,系統版本號自動遞增,事務開始的版本號作為事務的版本號,下面分別看看各種操作下這兩個版本號是如何控制併發的。
-
查詢操作(select)時,讀取建立時間小於等於事務版本且刪除時間未定義或大於事務版本的那些行,翻譯成人話就是隻讀取本次事務新增或之前就存在,並且至少截止到本次事務還沒有刪除的那些記錄。
-
插入時(insert),行的建立時間設定為當前系統版本號。
- 刪除時(delete),刪除時間設為系統版本號。
- 修改時(update),將當前版本號作為新行的建立時間和舊行的刪除時間,可見修改相當於刪除和插入兩個動作。
回過頭看上面A第二次讀取時如果按這種方式就不會出現幻行,因為A只會讀取A之前就存在和A自身插入的行。