事前準備資料
mysql> create table city(
-> id int(10) auto_increment,
-> name varchar(30),
-> primary key (id)
-> )engine=innodb charset=utf8mb4;
insert into city(name) values('武漢市');
mysql> select * from city;
+----+-----------+
| id | name |
+----+-----------+
| 1 | 武漢市 |
+----+-----------+
事務并發可能出現的情況
髒讀(Dirty Read)
一個事務讀到了另一個未送出事務修改過的資料

會話B開啟一個事務,把id=1的name為武漢市修改成溫州市,此時另外一個會話A也開啟一個事務,讀取id=1的name,此時的查詢結果為溫州市,會話B的事務最後復原了剛才修改的記錄,這樣會話A讀到的資料是不存在的,這個現象就是髒讀。(髒讀隻在讀未送出隔離級别才會出現)
不可重複讀(Non-Repeatable Read)
一個事務隻能讀到另一個已經送出的事務修改過的資料,并且其他事務每對該資料進行一次修改并送出後,該事務都能查詢得到最新值。(不可重複讀在讀未送出和讀已送出隔離級别都可能會出現)
會話A開啟一個事務,查詢id=1的結果,此時查詢的結果name為武漢市。接着會話B把id=1的name修改為溫州市(隐式事務,因為此時的autocommit為1,每條SQL語句執行完自動送出),此時會話A的事務再一次查詢id=1的結果,讀取的結果name為溫州市。會話B再此修改id=1的name為杭州市,會話A的事務再次查詢id=1,結果name的值為杭州市,這種現象就是不可重複讀。
幻讀(Phantom)
一個事務先根據某些條件查詢出一些記錄,之後另一個事務又向表中插入了符合這些條件的記錄,原先的事務再次按照該條件查詢時,能把另一個事務插入的記錄也讀出來。(幻讀在讀未送出、讀已送出、可重複讀隔離級别都可能會出現)
會話A開啟一個事務,查詢id>0的記錄,此時會查到name=武漢市的記錄。接着會話B插入一條name=溫州市的資料(隐式事務,因為此時的autocommit為1,每條SQL語句執行完自動送出),這時會話A的事務再以剛才的查詢條件(id>0)再一次查詢,此時會出現兩條記錄(name為武漢市和溫州市的記錄),這種現象就是幻讀。
事務的隔離級别
MySQL的事務隔離級别一共有四個,分别是讀未送出、讀已送出、可重複讀以及可串行化。
MySQL的隔離級别的作用就是讓事務之間互相隔離,互不影響,這樣可以保證事務的一緻性。
隔離級别比較:可串行化>可重複讀>讀已送出>讀未送出
隔離級别對性能的影響比較:可串行化>可重複讀>讀已送出>讀未送出
由此看出,隔離級别越高,所需要消耗的MySQL性能越大(如事務并發嚴重性),為了平衡二者,一般建議設定的隔離級别為可重複讀,MySQL預設的隔離級别也是可重複讀。
讀未送出(READ UNCOMMITTED)
在讀未送出隔離級别下,事務A可以讀取到事務B修改過但未送出的資料。
可能發生髒讀、不可重複讀和幻讀問題,一般很少使用此隔離級别。
讀已送出(READ COMMITTED)
在讀已送出隔離級别下,事務B隻能在事務A修改過并且已送出後才能讀取到事務B修改的資料。
讀已送出隔離級别解決了髒讀的問題,但可能發生不可重複讀和幻讀問題,一般很少使用此隔離級别。
可重複讀(REPEATABLE READ)
在可重複讀隔離級别下,事務B隻能在事務A修改過資料并送出後,自己也送出事務後,才能讀取到事務B修改的資料。
可重複讀隔離級别解決了髒讀和不可重複讀的問題,但可能發生幻讀問題。
提問:為什麼上了寫鎖(寫操作),别的事務還可以讀操作?
因為InnoDB有MVCC機制(多版本并發控制),可以使用快照讀,而不會被阻塞。
可串行化(SERIALIZABLE)
各種問題(髒讀、不可重複讀、幻讀)都不會發生,通過加鎖實作(讀鎖和寫鎖)。
隔離級别的實作原理
使用MySQL的預設隔離級别(可重複讀)來進行說明。
每條記錄在更新的時候都會同時記錄一條復原操作(復原記錄檔undo log)。同一條記錄在系統中可以存在多個版本,這就是資料庫的多版本并發控制(MVCC)。即通過復原(rollback操作),可以回到前一個狀态的值。
假設一個值從 1 被按順序改成了 2、3、4,在復原日志裡面就會有類似下面的記錄。
目前值是 4,但是在查詢這條記錄的時候,不同時刻啟動的事務會有不同的 read-view。如圖中看到的,在視圖 A、B、C 裡面,這一個記錄的值分别是 1、2、4,同一條記錄在系統中可以存在多個版本,就是資料庫的多版本并發控制(MVCC)。對于 read-view A,要得到 1,就必須将目前值依次執行圖中所有的復原操作得到。
同時你會發現,即使現在有另外一個事務正在将 4 改成 5,這個事務跟 read-view A、B、C 對應的事務是不會沖突的。
提問:復原記錄檔(undo log)什麼時候删除?
MySQL會判斷當沒有事務需要用到這些復原日志的時候,復原日志會被删除。
提問:什麼時候不需要了?
當系統裡麼有比這個復原日志更早的read-view的時候。
檢視目前會話隔離級别
方式1
指令:SHOW VARIABLES LIKE 'transaction_isolation';
mysql> show variables like 'transaction_isolation';
+-----------------------+--------------+
| Variable_name | Value |
+-----------------------+--------------+
| transaction_isolation | SERIALIZABLE |
+-----------------------+--------------+
方式2
指令:SELECT @@transaction_isolation;
mysql> select @@transaction_isolation;
+-------------------------+
| @@transaction_isolation |
+-------------------------+
| SERIALIZABLE |
+-------------------------+
設定隔離級别
方式1:通過set指令
SET [GLOBAL|SESSION] TRANSACTION ISOLATION LEVEL level;
其中level有4種值:
level: {
REPEATABLE READ
| READ COMMITTED
| READ UNCOMMITTED
| SERIALIZABLE
}
關鍵詞:GLOBAL
SET GLOBAL TRANSACTION ISOLATION LEVEL level;
* 隻對執行完該語句之後産生的會話起作用
* 目前已經存在的會話無效
關鍵詞:SESSION
SET SESSION TRANSACTION ISOLATION LEVEL level;
* 對目前會話的所有後續的事務有效
* 該語句可以在已經開啟的事務中間執行,但不會影響目前正在執行的事務
* 如果在事務之間執行,則對後續的事務有效。
無關鍵詞
SET TRANSACTION ISOLATION LEVEL level;
* 隻對目前會話中下一個即将開啟的事務有效
* 下一個事務執行完後,後續事務将恢複到之前的隔離級别
* 該語句不能在已經開啟的事務中間執行,會報錯的
方式2:通過服務啟動項指令
可以修改啟動參數transaction-isolation的值
比方說我們在啟動伺服器時指定了--transaction-isolation=READ UNCOMMITTED,那麼事務的預設隔離級别就從原來的REPEATABLE READ變成了READ UNCOMMITTED。