MySQL中事務的隔離級別如何實現
事務併發可能產生的問題:(在不考慮事務隔離的情況下)
髒讀:
髒讀是指在一個事務處理過程裡讀取了另一個未提交的事務中的資料。
不可重複讀:
不可重複讀是指在對於資料庫中的某個資料,一個事務範圍內多次查詢卻返回了不同的資料值,這是由於在查詢間隔,被另一個事務修改並提交了。
幻讀/虛讀:
是指當事務不是獨立執行時發生的一種現象,例如第一個事務對一個表中的資料進行了修改,這種修改涉及到表中的全部資料行。同時,第二個事務也修改這個表中的資料,這種修改是向表中插入一行新資料。那麼,以後就會發生操作第一個事務的使用者發現表中還有沒有修改的資料行,就好象發生了幻覺一樣。
不可重複讀與幻讀的區別:
不可重複讀的重點是修改,同樣的條件,你讀取過的資料,再次讀取出來發現值不一樣;(主要在於update)
幻讀的重點在於新增或者刪除,同樣的條件,第 1 次和第 2 次讀出來的記錄數不一樣。(主要在於insert和delete)
MVCC(多版本併發控制)
MVCC就是為了實現讀寫衝突不加鎖
多版本併發控制,顧名思義,在併發訪問的時候,資料存在版本的概念,可以有效地提升資料庫併發能力,常見的資料庫如MySQL、MS SQL Server、IBM DB2、Hbase、MongoDB等等都在使用。
簡單講,如果沒有MVCC,當想要讀取的資料被其他事務用排它鎖鎖住時,只能互斥等待;而這時MVCC可以通過提供歷史版本從而實現讀取被鎖的資料的歷史版本,從而避免了互斥等待。
InnoDB採用的MVCC實現方式是:在需要時,通過undo日誌構造出歷史版本。
事務的隔離級別:
為了解決上面說的併發所導致的問題,就需要設定資料的隔離級別,事務的隔離級別是通過鎖、MVCC的方式實現
Read uncommitted:讀未提交,最低級別,以上情況都無法保證
實現機制:在前文有說到所有寫操作都會加排它鎖,那還怎麼讀未提交呢?因為排他鎖會阻止其它事務再對其鎖定的資料加讀或寫的鎖,但是對不加鎖的讀就不起作用了。READ UNCOMMITTED隔離級別下, 讀不會加任何鎖。而寫會加排他鎖,併到事務結束之後釋放。
Read committed:讀已提交,防止資料髒讀
實現機制:事務中的修改操作會加排他鎖,直到事務提交時才釋放鎖。讀取資料不加鎖而是使用了MVCC機制。因此在讀已提交的級別下,都會通過MVCC獲取當前資料的最新快照,不加任何鎖,也無視任何鎖(因為歷史資料是構造出來的,身上不可能有鎖)。
為什麼Read committed可以防止資料髒讀:髒讀是因為讀取了其他事務未提交的資料,之後事務回滾了,導致髒讀。但是如果在事務中修改資料時加了排他鎖,並且直到事務提交時才釋放排他鎖,在這之間不允許其他事務查詢此記錄,所以不會出現髒讀。
為什麼遺留了不可重複讀和幻讀問題:MVCC版本的生成時機: 是每次select時。這就意味著,如果我們在事務A中執行多次的select,在每次select之間有其他事務更新了我們讀取的資料並提交了,那就出現了不可重複讀,即:重複讀時,會出現資料不一致問題,後面我們會講解超支現象,就是這種引起的。
Repeatable read:
實現機制:READ COMMITTED級別不同的是MVCC版本的生成時機,即:一次事務中只在第一次select時生成版本,後續的查詢都是在這個版本上進行,從而實現了可重複讀。
Serializable:
實現機制:所有的讀操作均為當前讀,讀加讀鎖 (S鎖),寫加寫鎖 (X鎖)。採用的是範圍鎖RangeS RangeS_S模式,鎖定檢索範圍為只讀,這樣就避免了幻影讀問題。
Serializable隔離級別下,讀寫衝突,因此併發度急劇下降,在MySQL/InnoDB下不建議使用。
檢視當前資料庫隔離級別:
show global variables like '%isolation%';
MySQL資料庫設定事務隔離級別:(設定資料庫的隔離級別要在開啟事務之前)
set [global | session] transaction isolation level 隔離級別名稱; 或 set tx_isolation='隔離級別名稱'
set session transaction isolation level read uncommitted; -- 設定read uncommitted級別
set session transaction isolation level read committed; -- 設定read committed級別
set session transaction isolation level repeatable read; -- 設定repeatable read級別
set session transaction isolation level serializable; -- 設定serializable級別
ADO.NET設定事務隔離級別:
var tran = conn.BeginTransaction(IsolationLevel.ReadUncommitted)
測試幾種隔離級別:
read uncommitted:
開啟兩個會話,先後執行會話1、會話2中的程式碼
會話1:
set autocommit=0; -- 設定不自動提交
update actor set first_name='fan' where actor_id=1; -- 將姓名修改為fan,不提交
會話2:
set session transaction isolation level read uncommitted; -- 將當前會話隔離級別設定為read uncommitted
select * from actor where actor_id=1; -- 可以讀取到會話1修改後的資料
read committed:
會話1:
set autocommit=0;
update actor set first_name='fan' where actor_id=1;
會話2:
set session transaction isolation level read committed; -- 將當前會話隔離級別設定為read committed
select * from actor where actor_id=1; -- 會話2讀取到的還是原來的資料,直到會話1提交後,會話2才可以讀到修改後的資料
再測試一下是否可以重複讀:
會話1:
set session transaction isolation level read committed;
set autocommit=0;
select * from actor where actor_id=1; -- 先執行這個sql,檢視結果。然後再執行會話2
select * from actor where actor_id=1; -- 執行完會話2後再執行一次查詢,對比兩次結果是否一致
會話2:
update actor set first_name='fan' where actor_id=1; -- 將姓名修改為fan
結果是會話1中兩次查詢結果不一致
repeatable read:
會話1:
set session transaction isolation level repeatable read;
set autocommit=0;
select * from actor where actor_id=1; -- 先執行這個sql,檢視結果。然後再執行會話2
select * from actor where actor_id=1; -- 執行完會話2後再執行一次查詢,對比兩次結果是否一致
會話2:
update actor set first_name='fan' where actor_id=1; -- 將姓名修改為fan
結果是會話1中兩次查詢結果一致
serializable:
會話1:
set session transaction isolation level serializable;
set autocommit=0;
select * from actor where actor_id=1;
會話2:
update actor set first_name='fan' where actor_id=1;