Mysql 四種事務隔離級別

1、前提

  時過一年從新拾起博文記錄,但願後面都能堅持下來。 接着以前MySql的學習,先記錄下這篇。javascript

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

2、事務的ACID特性

  1. A(Atomicity) 原子性

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

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

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

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

3、事務的4種隔離級別

  • Read Uncommitted - 未提交讀

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

設置隔離級別java

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

開啓事務1mysql

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中未提交事務中的數據,這就是髒讀。sql

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依然顯示爲空session

(沒有讀取到事務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 - 可重複讀

該隔離級別是MySQL默認的隔離級別,在同一個事務裏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 引擎會給讀操做隱式加一把讀共享鎖,從而避免了髒讀、不可重讀復讀和幻讀問題。
 

四 總結

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

  二、不可重複讀:一個事務能夠讀取另外一個已提交的事務,屢次讀取會形成不同的結果。這種現象也被稱爲不可重複讀

  三、幻讀:基於可重複讀的基礎上查詢結果是同樣的,可是當對某些行進行更新或者插入時卻會受到影響操做不了,就造成了幻讀。

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

五 參考文獻

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

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

相關文章
相關標籤/搜索