上一篇MySQL的文章講了事務的特性和原理(
最詳細的MySQL事務特性及原理講解!(一)
) 作為基礎,小伙伴們自行查看。
關于MySQL隔離級別的講解,網上已經很多了。和大家一樣,我也看過很多很多,但是發現看完之后還是暈暈乎乎,學習走了很多彎路。事務特性的原理是什么?什么隔離級別可以解決什么問題?具體什么原理?這些似乎都沒有講清楚,無非是抄來抄取,甚至都抄錯了,我查了近10篇文章每篇都有各種各樣的錯誤!比如下面這個舉例:
作者說RR解決了幻讀的問題,并且還不止一個人這樣說。還有說未提交讀級別下不加鎖。
那么事實是這樣嗎?(閱讀提醒:點收藏前先點「在看」,記憶效果翻倍!)
隔離性
與原子性、持久性側重于研究事務本身不同,隔離性研究的是不同事務之間的相互影響。隔離性是指,事務內部的操作與其他事務是隔離的,并發執行的各個事務之間不能互相干擾。
嚴格的隔離性,對應了事務隔離級別中的Serializable (可串行化),但實際應用中出于性能方面的考慮很少會使用可串行化。隔離性追求的是并發情形下事務之間互不干擾。SQL標準定義了4類隔離級別:Read Uncommitted(讀取未提交內容)、Read Committed(讀取提交內容)、Repeatable Read(可重讀)、Serializable(可串行化)。
對于隔離性的探討,主要可以分為兩個方面:
1.鎖機制保證隔離性
2.MVCC保證隔離性。
隔離級別原理及問題
嚴格的隔離性,對應了事務隔離級別中的Serializable (可串行化),但實際應用中出于性能方面的考慮很少會使用可串行化。所以,共有四種隔離級別來對應不同的業務性能需求。然而正是由于不同的隔離級別,便產生了不同的問題。
我們不妨帶著幾個問題去繼續閱讀:
1.可重復讀和幻讀的區別是什么?
2.每個隔離級別解決了怎樣的問題?怎么解決的?
1、讀未提交(READ_UNCOMMITTED)
顧名思義,該隔離級別下可以讀取到另一個事務未提交的數據。嚴謹一點,我們給出官方定義:
The isolation level that provides the least amount of protection between transactions. Queries employ a locking strategy that allows them to proceed in situations where they would normally wait for another transaction. However, this extra performance comes at the cost of less reliable results, including data that has been changed by other transactions and not committed yet (known as dirty read). Use this isolation level with great caution, and be aware that the results might not be consistent or reproducible, depending on what other transactions are doing at the same time. Typically, transactions with this isolation level only do queries, not insert, update, or delete operations.
如文檔所說,它提供了最低的隔離級別,查詢時采用無需等待其他事務的鎖策略。所以,在這個隔離級別下,基本上什么問題都會產生了。于是文檔建議,通常具有此隔離級別的事務僅執行查詢,而不執行插入,更新或刪除操作。
但是,這并不代表他一無是處。其實RU隔離級別解決了一個問題——當A事務修改數據n,B事務也修改數據n,后執行的語句會把另一個事務的結果覆蓋。如文章開頭提到的反例,如果讀未提交隔離級別下不加鎖,這是不可能實現的。
為了解決丟失修改的寫覆蓋問題,未提交讀規定:
1.事務A對當前被讀取的數據不加鎖,事務B讀取也不加鎖。
2.事務A開始更新一行數據時,必須先對其加排他鎖,直到事務結束才釋放。
如何證明呢?
事務A事務B
發生臟讀 和 等待
圖中左邊為事務A,右邊為事務B,可見事務A更新時加排他鎖,事務未提交,所以事務B更新數據發生阻塞。但是B卻可以讀取到A修改后的數據,說明B在讀取時并未加任何鎖。
如圖,證明兩個事務在更新時發生了排他鎖競爭。
臟讀(DIRTY READ)
那么未提交讀會產生什么問題?剛才的例子依舊說明了,發生了臟讀。如圖:
右邊事務讀取到了左邊事務更改后的數據 a=11,但左邊事務還未提交。
我們依舊給出臟讀的定義:
An operation that retrieves unreliable data, data that was updated by another transaction but not yet committed. It is only possible with the isolation level known as read uncommitted.
檢索不可靠數據的操作,該數據是由另一個事務更新但尚未提交的數據。只有在隔離級別稱為未提交讀的情況下才有可能。
劃重點!讀出來的數據不可靠,是可以被其他事務修改后未提交的數據。臟讀只可能出現在未提交讀情況下。
2、讀已提交(READ_COMMITTED)
讀已提交和讀未提交最大的區別如名字所說,可以讀到另一個事務已提交讀數據。官方定義如下:
An isolation level that uses a locking strategy that relaxes some of the protection between transactions, in the interest of performance. Transactions cannot see uncommitted data from other transactions, but they can see data that is committed by another transaction after the current transaction started. Thus, a transaction never sees any bad data, but the data that it does see may depend to some extent on the timing of other transactions.
事務無法看到來自其他事務的未提交的數據,但是它們可以看到在當前事務開始后另一個事務提交的數據。因此,一個事務永遠不會看到任何不良數據,但是它所看到的數據可能在某種程度上取決于其他事務的時間安排。
實驗前,更改數據庫隔離級別為RC。
mysql> set session transaction isolation level read committed;Query OK, 0 rows affected (0.01 sec)
mysql> SELECT @@tx_isolation;+----------------+| @@tx_isolation |+----------------+| READ-COMMITTED |+----------------+1 row in set, 1 warning (0.00 sec)
事務A
事務B
臟讀被解決
不可重復讀出現
如上表AB事務可知,在B事務更改數據 a=11 且事務未提交時,A事務讀取到的還是舊數據 a=9,可見RC隔離級別很好的解決了臟讀的問題。
但是緊接著在B事務提交后,A事務再次讀取數據,a=11。在同一事務中兩次查詢出現了結果不一樣的情況,這就是不可重復讀。官方定義:
non-repeatable read:The situation when a query retrieves data, and a later query within the same transaction retrieves what should be the same data, but the queries return different results (changed by another transaction committing in the meantime).
查詢檢索數據,而同一事務中的前后查詢檢索應為相同的數據,但實際查詢返回不同的結果(數據同時被其他提交的事務更改)。
很關鍵的一點,不可重復讀是同一事務前后同樣的查詢返回了不同的數據!一定有人有這樣的疑惑,那臟讀不也是前后不一致嗎,怎么區分這兩者?很多文章只甩出了幾個臟讀、不可重復讀、幻讀的例子,卻并沒有解釋清楚到底什么是什么。
現在我可以肯定的說,臟讀的條件不可重復讀一定滿足,但是如果是不可重復讀,臟讀卻不一定滿足,臟讀屬于不可重復讀。可以這么理解,RC級別下,相當于只解決了一部分不可重復讀的問題。
他是如何解決臟讀的呢,鎖機制:
1.事務A對當前被讀取的數據加共享鎖,一旦讀完該行,立即釋放該共享鎖(注意是讀完立即釋放)
2.事務A在更新某行數據時,必須對其加上排他鎖,直到事務結束才釋放(注意是事務結束才釋放)
3、可重復讀(REPEATABLE_READ)
聽起來,可重復讀解決了不可重復讀的問題。事實是這樣。官方定義:
The default isolation level for InnoDB. It prevents any rows that are queried from being changed by other transactions, thus blocking non-repeatable reads but not phantom reads. It uses a moderately strict locking strategy so that all queries within a transaction see data from the same snapshot, that is, the data as it was at the time the transaction started.
InnoDB的默認隔離級別。它可防止查詢的任何行被其他事務更改,從而阻止不可重復的讀取,但不會阻止幻讀。它使用中等嚴格的鎖定策略,以便事務中的所有查詢都可以查看來自同一快照的數據,即事務開始時的數據。
看重點!RR隔離級別是InnoDB默認隔離級別,他可以解決不可重復讀,但不能解決幻讀!這就是為什么我說很多文章錯了,官方已經給出了明確定義。
mysql> set session transaction isolation level repeatable read;Query OK, 0 rows affected (0.00 sec)
mysql> SELECT @@tx_isolation;+-----------------+| @@tx_isolation |+-----------------+| REPEATABLE-READ |+-----------------+1 row in set, 1 warning (0.00 sec)
如表格所示,不可重復讀是被解決了,但很明顯還有一個問題,沒有解決幻讀。
事務A事務B
已解決不可重復讀
此時我們再來一次,回到B事務提交前但狀態,執行一條insert語句。
回到更新前狀態。
在B事務執行 insert 后再次查詢
B事務提交后出現了奇怪的情況。A事務查詢到的數據前后一致,但是在UPDATE條件下符合的數據卻變成兩條。
A row that Appears in the result set of a query, but not in the result set of an earlier query. For example, if a query is run twice within a transaction, and in the meantime, another transaction commits after inserting a new row or updating a row so that it matches the WHERE clause of the query.
在查詢的結果集中出現的行,但在較早的查詢的結果集中沒有出現。例如,如果一個查詢在一個事務中運行兩次,同時,另一個事務將在插入新行或更新一行以使其與查詢的WHERE子句匹配之后提交。
請看上方幻讀定義。重點是,同樣的條件同一事務前后結果中出現了和之前不一樣的行。與不可重復讀的區別是什么:幻讀的條件也符合不可重復讀,但是不可重復讀不一定符合幻讀。前后檢索出不一樣的行當然查詢前后查詢結果不同,但是查詢結果不同不一定有新的行。
為什么很多文章錯誤的認為RR可以解決幻讀呢?我也不知道,可能是被快照讀迷惑了?MVCC能解決快照讀下的幻讀問題,但是沒法保證當前讀下的幻讀問題(這個我們下一篇細說,內容太多了)。但是幻讀確實在一定條件下解決,這個條件就是 next-key locks。
By default, InnoDB operates in REPEATABLE READ transaction isolation level. In this case, InnoDB uses next-key locks for searches and index scans, which prevents phantom rows
默認情況下,InnoDB以REPEATABLE READ事務隔離級別運行。在這種情況下,InnoDB使用next-key locks 鎖定進行搜索和索引掃描,從而防止幻讀
使用效果如下:
select count(*) from test where id>=9 lock in share mode;
執行 insert 發生阻塞,如此便使用了 next-key locks 防止幻讀。
鎖原理:
1.事務A在讀取某數據時,必須先對其加共享鎖,直到事務結束才釋放(注意是事務結束才釋放)
2.事務A在更新某數據時,必須先對其加排他鎖,直到事務結束才釋放(注意是事務結束才釋放)
4、串行讀(SERIALIZABLE_READ)
The isolation level that uses the most conservative locking strategy, to prevent any other transactions from inserting or changing data that was read by this transaction, until it is finished. This way, the same query can be run over and over within a transaction, and be certain to retrieve the same set of results each time. Any attempt to change data that was committed by another transaction since the start of the current transaction, cause the current transaction to wait.
最嚴格的隔離級別,防止其他任何事務插入或更改此事務讀取的數據,直到完成為止。這樣,可以在一個事務中一遍又一遍地運行相同的查詢,并確保每次都檢索相同的結果集。自當前事務開始以來,任何嘗試更改另一事務提交的數據的嘗試都會導致當前事務等待。
這個我就簡單演示下,因為不會出現任何問題,但是效率也最低。
如圖,執行串行讀級別下,insert 發生阻塞,隔離級別最嚴格。
鎖機制:
1.事務在讀取數據時,必須先對其加表級共享鎖(注意這里是表級) ,直到事務結束才釋放。
2.事務在更新數據時,必須先對其加表級排他鎖(注意這里是表級) ,直到事務結束才釋放。
總結
到這里,我們之前的問題也有了答案。
1.不同隔離級別解決怎樣的問題:
隔離級別臟讀不可重復讀幻讀讀未提交發生發生發生讀已提交?發生發生可重復讀??可以在 next-key lock下解決
串行讀???
2.臟讀、不可重復讀、幻讀關系