1. 程式人生 > 其它 >MySQL事務隔離機制

MySQL事務隔離機制

今日內容概述

1.InnoDB儲存引擎的鎖機制
2.多版本併發控制MVCC
3.MySQL事務隔離機制

今日內容詳細

1.InnoDB儲存引擎的鎖機制

MyISAM和MEMORY採用表級鎖(table-level locking)。

BDB採用頁級鎖(page-level locking)或表級鎖,預設為頁級鎖。

InnoDB支援行級鎖(row-level locking)和表級鎖,預設為行級鎖(偏向於寫)。

InnoDB的鎖定模式實際上可以分為四種:共享鎖(S),排他鎖(X),意向共享鎖(IS)和意向排他鎖(IX),我們可以通過以下表格來總結上面這四種所的共存邏輯關係:

如果一個事務請求的鎖模式與當前的鎖相容,InnoDB就將請求的鎖授予該事務;反之,如果兩者不相容,該事務就要等待鎖釋放。

行級鎖以表級鎖的使用區別

MyISAM 操作資料都是使用表級鎖,MyISAM總是一次性獲得所需的全部鎖,要麼全部滿足,要麼全部等待。所以不會產生死鎖,但是由於每操作一條記錄就要鎖定整個表,導致效能較低,併發不高。

InnoDB 與 MyISAM 的最大不同有兩點:一是 InnoDB 支援事務;二是 InnoDB 採用了行級鎖。也就是你需要修改哪行,就可以只鎖定哪行。

在Mysql中,行級鎖並不是直接鎖記錄,而是鎖索引。InnoDB 行鎖是通過給索引項加鎖實現的,而索引分為主鍵索引和非主鍵索引兩種

1、如果一條sql 語句操作了主鍵索引,Mysql 就會鎖定這條語句命中的主鍵索引(或稱聚簇索引);

2、如果一條語句操作了非主鍵索引(或稱輔助索引),MySQL會先鎖定該非主鍵索引,再鎖定相關的主鍵索引。

3、如果沒有索引,InnoDB會通過隱藏的聚簇索引來對記錄加鎖。也就是說:如果不通過索引條件檢索資料,那麼InnoDB將對錶中所有資料加鎖,實際效果跟表級鎖一樣。

# 在實際應用中,要特別注意InnoDB行鎖的這一特性,不然的話,可能導致大量的鎖衝突,從而影響併發效能。
1、在不通過索引條件查詢的時候,InnoDB 的效果就相當於表鎖
2、當表有多個索引的時候,不同的事務可以使用不同的索引鎖定不同的行,另外,不論 是使用主鍵索引、唯一索引或普通索引,InnoDB 都會使用行鎖來對資料加鎖。
3、由於 MySQL 的行鎖是針對索引加的鎖,不是針對記錄加的鎖,所以即便你的sql語句訪問的是不同的記錄行,但如果命中的是相同的被鎖住的索引鍵,也還是會出現鎖衝突的。
4、即便在條件中使用了索引欄位,但是否使用索引來檢索資料是由 MySQL 通過判斷不同 執行計劃的代價來決定的,如果 MySQL 認為全表掃 效率更高,比如對一些很小的表,它 就不會使用索引,這種情況下 InnoDB 將鎖住所有行,相當於表鎖。因此,在分析鎖衝突時, 別忘了檢查 SQL 的執行計劃,以確認是否真正使用了索引。

三種行鎖的演算法

InnoDB有三種行鎖的演算法,都屬於排他鎖:
1、Record Lock:單個行記錄上的鎖。
2、Gap Lock:間隙鎖,鎖定一個範圍,但不包括記錄本身。GAP鎖的目的,是為了防止同一事務的兩次當前讀,出現幻讀的情況。
3、Next-Key Lock:等於Record Lock結合Gap Lock,也就說Next-Key Lock既鎖定記錄本身也鎖定一個範圍,特別需要注意的是,InnoDB儲存引擎還會對輔助索引下一個鍵值加上gap lock。

什麼時候使用表鎖

絕大部分情況下使用表鎖,但在個別特殊事務中,也可以考慮使用表鎖
1、事務需要更新大部分資料,表又較大
若使用預設的行鎖,不僅該事務執行效率低(因為需要對較多行加鎖,加鎖是需要耗時的); 而且可能造成其他事務長時間鎖等待和鎖衝突; 這種情況下可以考慮使用表鎖來提高該事務的執行速度
2、事務涉及多個表,較複雜,很可能引起死鎖,造成大量事務回滾
這種情況也可以考慮一次性鎖定事務涉及的表,從而避免死鎖、減少資料庫因事務回滾帶來的開銷當然,應用中這兩種事務不能太多,否則,就應該考慮使用MyISAM。

行鎖優化建議

通過檢查InnoDB_row_lock狀態變數來分析系統上的行鎖的爭奪情況,在著手根據狀態量來分析改善;
show status like 'innodb_row_lock%';//檢視行鎖的狀態

儘可能讓所有資料檢索都通過索引來完成, 從而避免無索引行鎖升級為表鎖
合理設計索引,儘量縮小鎖的範圍
儘可能減少檢索條件,避免間隙鎖
儘量控制事務大小,減少鎖定資源量和時間長度
儘可能低級別事務隔離

2.多版本併發控制MVCC

MySQL儲存引擎實現的是基於多版本的併發控制協議---MVVCC
# 與MVCC相對的,是基於鎖的併發控制

MVCC最大的好處是:讀不加鎖,讀寫不衝突。在讀多寫少的OLTP應用中,讀寫不衝突是非常重要的,極大的增加了系統的併發效能,這也是為什麼現階段幾乎所有的RDBMS,都支援MVCC。

在MVCC併發控制中,讀操作可以分為兩類:快照讀與當前讀
快照讀讀取的是記錄的可見版本(有可能是歷史版本),不用加鎖。
當前讀,讀取的是記錄的最新版本,並且當前讀返回的記錄都會加上鎖,保證其他事務不會再併發修改這條記錄

# 哪些操作屬於快照讀,哪些屬於當前讀?
快照讀:簡單的select操作,屬於快照讀,不加鎖(也有例外)

當前讀:特殊的讀操作,插入/更新/刪除操作,屬於當前讀,需要加鎖
當前讀讀取記錄的最新版本,並且讀取之後還需要保證其他併發事務不能修改當前記錄。
對讀取的記錄加鎖,其中除了第一條語句,對讀取記錄加S鎖(共享鎖)外,其他的操作,都是加X鎖(排他鎖)

# MVCC原理
MVCC可以提供基於某個時間點的快照,使得對於事務來看,總是可以提供與事務開始時刻相一致的資料,而不管這個事務執行的時間有多長。所以在不同的事務看來,同一時刻看到的相同行的資料可能是不一樣的,即一個行可能有多個版本

3.MySQL事務隔離機制

事務具有原子性、一致性、隔離性、永續性四大特性,而隔離性顧名思義指的就是事務彼此之間隔離開,多個事務在同時處理一個數據時彼此之間互相不影響,如果隔離的不夠好就有可能會產生髒讀、不可重複度、幻讀等讀現象,為此,隔離性總共分為四種級別:

由低到高依次為Read uncommitted 、Read committed 、Repeatable read 、Serializable ,這四個級別可以逐個解決髒讀 、不可重複讀 、幻讀 這幾類問題.

隔離機制 特點 髒讀 不可重複讀 幻讀
Read uncommitted(獨立提交,未提交讀) 允許事務檢視其他事務所進行的未提交更改
Read committed(提交讀) 允許其他事務檢視已經提交的事務 ×
Repeatable read(可重複讀,innodb引擎預設) 確保每個事務的 SELECT 輸出一致 InnoDB 的預設級別 #commit之後,其他視窗看不到資料,必須退出重新登入檢視 × ×
Serializable(可序列化、序列化) 將一個事務於其他事務完全隔離,即序列化 #當一個事務沒有提交,查詢也不行。例如:我改微信頭像的時候你不能看我的資訊,我看你朋友圈的時候你不能發朋友圈也不能看朋友圈 × × ×
需要強調的是:我們確實可以採用提高事務的隔離級別的方式來解決髒讀、不可重複讀、幻讀等問題,但與此同時,事務的隔離級別越高,併發能力也就越低。所以,還需要讀者根據業務需要進行權衡。

未提交讀(Read uncommitted)

未提交讀(READ UNCOMMITTED)是最低的隔離級別。通過名字我們就可以知道,在這種事務隔離級別下,一個事務隔離級別下,一個事務可以讀到另一個事務未提交的資料

# 原理:
未提交讀的資料庫鎖情況(實現原理)
事務在讀資料的時候並未對資料加鎖。
事務在修改資料的時候只對資料增加行級共享鎖。

# 案例:
第一步:設定事務的隔離級別
mysql> set tx_isolation='READ-UNCOMMITTED';

第二步:開啟事務
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)

第三步:修改資料
mysql> update student set age = 17 where id = 1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

第四步:重新開啟一個事務
mysql> set tx_isolation='READ-UNCOMMITTED';
Query OK, 0 rows affected, 1 warning (0.00 sec)
				
mysql> show variables like '%tx_isolation%';
+---------------+------------------+
| Variable_name | Value            |
+---------------+------------------+
| tx_isolation  | READ-UNCOMMITTED |
+---------------+------------------+
1 row in set (0.02 sec)

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

第五步:在新的事務中查詢剛剛修改過的資料
mysql> select * from student;
+----+------------+-----+
| id | name       | age |
+----+------------+-----+
|  1 | 大D妹妹    |  17 |

# 結論:
由此可以得出,隔離機制為未提交讀時,一個事務修改的資料,另一個事務可以檢視到上一個事務未提交的資料。

提交讀(Read committed)

提交讀(READ COMMITTED)也可以翻譯成讀已提交,通過名字也可以分析出,在一個事務修改資料過程中,如果事務還沒提交,其他事務不能讀該資料。

提交讀的資料庫鎖情況
事務對當前被讀取的資料加行級共享鎖(當讀到時才加鎖),一旦讀完該行,立即釋放該行級共享鎖;事務在更新某資料的瞬間(就是發生更新的瞬間),必須先對其加行級排他鎖,直到事務結束才釋放。但是事務一旦提交,其他事務可以立即檢視到已經提交了的資料。

案例:
第一步:設定事務的隔離級別
mysql> set tx_isolation='READ-COMMITTED';
Query OK, 0 rows affected, 1 warning (0.00 sec)

mysql> show variables like '%tx_isolation%';
+---------------+----------------+
| Variable_name | Value          |
+---------------+----------------+
| tx_isolation  | READ-COMMITTED |
+---------------+----------------+
1 row in set (0.00 sec)

第二步:開啟事務
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
			
第三步:修改資料並提交		
mysql> update student set age = 17 where id = 1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0
			
mysql> commit;
Query OK, 0 rows affected (0.01 sec)
			
第四步:重新開啟一個事務	
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
	
第五步:在新的事務中查詢剛剛修改過的資料		
mysql> select * from student where id = 1;
+----+------------+-----+
| id | name       | age |
+----+------------+-----+
|  1 | 大D妹妹    |  17 |
+----+------------+-----+
1 row in set (0.00 sec)

# 結論:
由此可知,提交讀事務隔離機制是當事務修改資料之後,並且提交,其他事務可以立即檢視到已經提交了的資料。

可序列化(Serializable)

可序列化(Serializable)是最高的隔離級別,前面提到的所有的隔離級別都無法解決的幻讀,在可序列化的隔離級別中可以解決。

我們說過,產生幻讀的原因是事務一在進行範圍查詢的時候沒有增加範圍鎖(range-locks:給SELECT 的查詢中使用一個“WHERE”子句描述範圍加鎖),所以導致幻讀。
                                  
可序列化的資料庫鎖情況  
事務在讀取資料時,必須先對其加 表級共享鎖 ,直到事務結束才釋放;事務在更新資料時,必須先對其加 表級排他鎖 ,直到事務結束才釋放。

第一步:設定事務的隔離級別
mysql> set tx_isolation='SERIALIZABLE';
Query OK, 0 rows affected, 1 warning (0.00 sec)

mysql> show variables like '%tx_isolation%';
+---------------+--------------+
| Variable_name | Value        |
+---------------+--------------+
| tx_isolation  | SERIALIZABLE |
+---------------+--------------+
1 row in set (0.00 sec)

第二步:開啟事務
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)

第三步:修改資料
mysql> update student set age = 18 where id = 2;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

第四步:檢視該資料
mysql> select * from student where id = 2;
^C^C -- query aborted
ERROR 1317 (70100): Query execution was interrupted

第五步:修改改資料
mysql> update student set age = 17 where id = 2;
^C^C -- query aborted
ERROR 1317 (70100): Query execution was interrupted  

# 結論:
當事務的隔離機制為:可序列化機制時,一旦加上排它鎖,那麼其他事務將無法檢視也無法修改該資料。

修改事務隔離級別

在 MySQL 中,可以通過show variables like '%tx_isolation%'或select @@tx_isolation;語句來檢視當前事務隔離級別。

mysql> show variables like '%tx_isolation%';
+---------------+----------------+
| Variable_name | Value          |
+---------------+----------------+
| tx_isolation  | READ-COMMITTED |
+---------------+----------------+
1 row in set (0.00 sec)

MySQL 提供了 SET TRANSACTION 語句,該語句可以改變單個會話或全域性的事務隔離級別。語法格式如下:

SET [SESSION | GLOBAL] TRANSACTION ISOLATION LEVEL {READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE}

其中,SESSION 和 GLOBAL 關鍵字用來指定修改的事務隔離級別的範圍:
● SESSION:表示修改的事務隔離級別將應用於當前 session(當前 cmd 視窗)內的所有事務
● GLOBAL:表示修改的事務隔離級別將應用於所有 session(全域性)中的所有事務,且當前已經存在的 session 不受影響
● 如果省略 SESSION 和 GLOBAL,表示修改的事務隔離級別將應用於當前 session 內的下一個還未開始的事務。

任何使用者都能改變會話的事務隔離級別,但是隻有擁有 SUPER 許可權的使用者才能改變全域性的事務隔離級別。

如果使用普通使用者修改全域性事務隔離級別,就會提示需要超級許可權才能執行此操作的錯誤資訊,SQL 語句和執行結果如下:

mysql> select @@session.tx_isolation;
+------------------------+
| @@session.tx_isolation |
+------------------------+
| READ-UNCOMMITTED       |
+------------------------+
1 row in set, 1 warning (0.00 sec)

mysql> set global transaction isolation level repeatable read;
Query OK, 0 rows affected (0.00 sec)

mysql> select @@global.tx_isolation;
+-----------------------+
| @@global.tx_isolation |
+-----------------------+
| REPEATABLE-READ       |
+-----------------------+
1 row in set, 1 warning (0.00 sec)

# 永久修改:
vim /etc/my.cnf
[mysqld]
transaction_isolation=read-uncommit

退出MySQL並重啟MySQL服務即可