Mysql 四種事務隔離級別


一、前提

  時過一年重新拾起博文記錄,希望后面都能堅持下來。 接着之前MySql的學習,先記錄下這篇。

  以下都是基於mysql8 innodb存儲引擎進行分析的。  

二、事務的ACID特性

  1. A(Atomicity) 原子性

  指整個數據庫事務是不可分割的單位,整個事務中的所有操作要么全部提交成功,要么全部失敗回滾。只有使事務中所有的數據庫操作都執行成功,才算整個事務執行成功。否則事務中任何一個SQL語句執行失敗,那么這個事務就是執行失敗的, 已執行成功的SQL語句也必須撤銷,數據庫狀態應該退回到執行事務前的狀態。
  1. C(consistency) 一致性

  一致性事務將數據庫從一種狀態轉變為下一種一致的狀態。在事務開始之前和事務結束以后,數據庫的完整性約束沒有被破壞。
  例如:在表中有個字段為姓名為唯一約束,即在表中姓名不能重復。如果一個事務對姓名字段進行了修改,但是在事務提交或事務操作發生回滾后,表中的姓名變得非唯一了,這就破壞了事務的一致性要求,即事務將數據從一種狀態變為了一種不一致的狀態。
  1. I(isolation) 隔離性

  事務的隔離性要求每個讀寫事務的對象對其他事務的操作對象能相互分離,即該事務提交前對其他事務都不可見。通常可以使用鎖來實現。
  1. D(durability) 持久性

  事務一旦提交,其結果就是永久性的。即使發生宕機等故障,數據庫也能將數據恢復。
  需要注意的是:只能從事務本身的角度來保證結果的永久性,例如:在事務提交后,所有變化都是永久的。即使當數據因為崩潰而需要恢復時,也能保證恢復后提交的數據都不會丟失。但如果不是數據庫本身發生故障,而是一些外部的原因導致數據庫發生問題,則有可能是提交的數據丟失(RAID卡損壞)。
  因此持久性保證事務系統的高可靠性,而不是高可用性。對於高可用性的實現,事務本身並不能保證,需要一些系統共同配合完成。
 

三、事務的4種隔離級別

  • Read Uncommitted - 未提交讀

  在該隔離級別下的事務會讀取到其未提交事務的數據,此種現象也稱之為 臟讀
步驟 事務1 事務2
1

設置隔離級別

mysql> set @@session.transaction_isolation 
= 'READ-UNCOMMITTED'; Query OK, 0 rows affected (0.00 sec) mysql> select @@transaction_isolation; +-------------------------+ | @@transaction_isolation | +-------------------------+ | READ-UNCOMMITTED | +-------------------------+ 1 row in set (0.00 sec)
 
2

開啟事務1

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from t;
Empty set (0.00 sec)
 
3  
無需管隔離級別,只開啟事務2並插入記錄
mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> insert into t select 1, '1';
Query OK, 1 row affected (0.00 sec)
Records: 1  Duplicates: 0  Warnings: 0
 4

此時能查到事務2中未提交事務中的數據,這就是臟讀。

mysql> select * from t;
+------+------+
| id   | name |
+------+------+
|    1 | 1    |
+------+------+
1 row in set (0.00 sec)
 
   注意:設置隔離級別之前,記得查看當前隔離級別的key, 有可能版本不一樣對應的key不一樣。(另外表可以自己隨意選擇)
1 mysql> show variables like '%isolation%';
2 +-----------------------+------------------+
3 | Variable_name         | Value            |
4 +-----------------------+------------------+
5 | transaction_isolation | READ-UNCOMMITTED |
6 +-----------------------+------------------+
7 1 row in set (0.00 sec)
在實際的業務場景中應該都不允許臟讀出現,既然這么評價不好為什么還會出現呢? 但其實這個隔離級別下的數據庫並發性能是最好的。

 

  • Read Committed - 提交讀

一個事務可以讀取另一個已提交的事務,多次讀取會造成不一樣的結果。這種現象也被稱為 不可重復讀。舉例說明:
步驟 事務1 事務2
1 先設置隔離級別
mysql> set @@session.transaction_isolation 
= 'READ-COMMITTED'; Query OK, 0 rows affected (0.00 sec) mysql> select @@transaction_isolation; +-------------------------+ | @@transaction_isolation | +-------------------------+ | READ-COMMITTED | +-------------------------+ 1 row in set (0.00 sec)
 
2 開啟事務1,查詢t表記錄為空
mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from t;
Empty set (0.00 sec)
 
3  

無需管隔離級別,只開啟事務2並插入記錄

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> insert into t select 2, '2';
Query OK, 1 row affected (0.01 sec)
Records: 1  Duplicates: 0  Warnings: 0
4

繼續查詢表t依然顯示為空

(沒有讀取到事務2未提交的數據,所以不存在臟讀問題)

mysql> select * from t;
Empty set (0.00 sec)

 
5  

 提交事務2

mysql> commit;
6

繼續查詢表t,此時能查詢事務2已提交的數據記錄(出現前后查詢不一致)

mysql> select * from t;
+------+------+
| id   | name |
+------+------+
|    2 | 2    |
+------+------+
 
總結:在隔離級別中解決了臟讀問題,但存在不可重復讀的問題(事務中會讀取其他已提交事務中的數據) 
  • Repeatable Read - 可重復讀

該隔離級別是 Innodb默認的隔離級別:在同一個事務里select的結果是事務開始時間時間點的狀態,解決了不可重復讀問題。
但其中會出現幻讀現象:基於可重復讀的基礎上查詢結果是一樣的,但是當對某些行進行更新或者插入時卻會受到影響操作不了,就形成了幻讀。例如:
步驟 事務1 事務2
 1

設置隔離級別

mysql> set @@session.transaction_isolation 
= 'REPEATABLE-READ'; mysql> select @@transaction_isolation; +-------------------------+ | @@transaction_isolation | +-------------------------+ | REPEATABLE-READ | +-------------------------+
 
 2

 開啟事務1,並查詢

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from t;
+------+------+
| id   | name |
+------+------+
|    3 | 3    |
+------+------+
 
 3  

 無需管隔離級別,只開啟事務2並插入記錄

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> insert into t select 4, '4';
Query OK, 1 row affected (0.01 sec)
Records: 1  Duplicates: 0  Warnings: 0
繼續查詢表t依然只有一條記錄id=3

(沒有讀取到事務2未提交的數據,所以不存在臟讀問題)

mysql> select * from t;
+------+------+
| id   | name |
+------+------+
|    3 | 3    |
+------+------+
 
 5    
將第二個窗口中的事務提交。

mysql> commit;
 6 繼續查詢表t依然只有一條記錄id=3

(沒有讀取到事務2已提交的數據,所以不存在不可重復讀問題)

mysql> select * from t;
+------+------+
| id   | name |
+------+------+
|    3 | 3    |
+------+------+
 
 7  
接着插入一條4的記錄,但提示插入不了,提示主鍵沖突問題。
然而查詢卻沒有這條id=4記錄。 這就是 幻讀現象
mysql> insert into t select 4, '4';
ERROR 1062 (23000): Duplicate entry '4' for key 'PRIMARY'
mysql> select * from t;
+----+------+
| id | name |
+----+------+
|  3 | 3    |
+----+------+
1 row in set (0.00 sec)
 
 8   另一種幻讀現象:接着上面操作,不插入記錄只更新記錄,將name 更新成 '5'后,發現有兩行受影響
   
mysql> update t set name = '5';
Query OK, 2 rows affected (0.00 sec)
Rows matched: 2  Changed: 2  Warnings: 0

mysql> select * from t;
+----+------+
| id | name |
+----+------+
|  3 | 5    |
|  4 | 5    |
+----+------+
 
總結:在隔離級別中解決了臟讀問題、不可重復讀的問題,但會存在幻讀問題。
但Innodb引擎提供了間隙鎖:innodb-next-key-locks, 解決了幻讀問題。基於上面結果演示下:
步驟 事務1 事務2
9

查詢時加上間隙鎖

mysql> begin;
mysql> select * from t where id  > 0 for update;
+----+------+
| id | name |
+----+------+
|  3 | 5    |
|  4 | 5    |
+----+------+

 

 
 10  

 插入記錄為6的數據就會發現插入這條記錄獲取鎖超時,自動異常

insert into t select 6, '6';
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

 

這樣成功的避免了幻讀問題,阻止了其他事務可能影響到我當前事務所涉及到的數據范圍。

  • Serializable - 可串行化

在該隔離級別下事務都是串行順序執行的,MySQL 數據庫的 InnoDB 引擎會給讀操作隱式加一把讀共享鎖,從而避免了臟讀、不可重讀復讀和幻讀問題。
 

四、總結

  1、臟讀:在一個事務中會讀取到其未提交事務的數據,此種現象也稱之為臟讀

  2、不可重復讀:一個事務可以讀取另一個已提交的事務,多次讀取會造成不一樣的結果。這種現象也被稱為不可重復讀

  3、幻讀:基於可重復讀的基礎上查詢結果是一樣的,但是當對某些行進行更新或者插入時卻會受到影響操作不了,就形成了幻讀。

隔離級別
臟讀
不可重復讀
幻讀
讀未提交(uncommitted read)
可能出現
可能出現
可能出現
讀提交(committed read)
不會出現
可能出現
可能出現
可重復讀(Repeatable Read)
不會出現
不會出現
可能出現(加上間隙鎖就不會)
可串行化(Serializable)
不會出現
不會出現
不會出現

五、參考文獻

《MySql 技術內幕(Innodb)第二版》

https://dev.mysql.com/doc/refman/8.0/en/innodb-transaction-isolation-levels.html

關注公眾號

免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2023 CODEPRJ.COM