SQL 事務級別 與 支持 圖解邏輯記錄
MySQl 專欄持續更新 不説晦澀難懂的東西 儘量輸出容易理解 和 使用的SQL技巧 和 初中級開發不是很常用的但很有用的知識
歡迎查看👉🏻👉🏻👉🏻SQL 專欄 查漏補缺 指教一二
![]()
前言
事務這個東西基本都瞭解 但是很多人不能將整個邏輯串聯起來 可能大部分人就知道ACID 但是事務的知識遠遠不止ACID 怎麼能抗住追問 和 完善自己的知識目錄 這個還是不錯的 建議認真看完
SQL 標準事務隔離級別
SQL 標準中規定了 4 種事務隔離級別,在多事務併發時可以避免一些事務併發問題。
read uncommitted
read uncommitted(讀未提交):簡稱 RU 級別,這個隔離級別是不允許髒寫發生的,也就是説不允許兩個事務在沒提交的情況下去更新同一行數據,卻允許讀取未提交的數據。但是依舊可能發生髒讀、不可重複讀、幻讀等問題。所以一般情況下都不會使用這個級別。
read committed
read committed(讀已提交):簡稱 RC 級別,這個隔離級別是不允許髒寫和髒讀的,也就是説不允許讀取和修改事務未提交的值。但是依舊可能發生不可重複讀和幻讀等問題,因為在事務期間內別的事務修改數據並提交後,還是會被這個事務讀取到。
repeatable read
repeatable read(可重複讀):簡稱 RR 級別,這個隔離級別是不允許髒寫、髒讀和不可重複讀發生的,也就是説不會在事務內讀到被其它事務修改的值,哪怕別的事務已提交。但是依舊有可能發生幻讀問題,因為它不保證別的事務新增或刪除數據後,這個事務不被影響。
serializable
serializable(串行化):簡稱 S 級別,這個隔離級別可以避免所有多事務問題,因為這個隔離級別下,所有的事務都將串行化,不會併發執行事務。但是這將會嚴重降低數據庫的性能,所以這個級別一般也不會用。
綜上,一般用的最多的就是 RC、RR 級別。並且在 MySQL 中 RR 級別已經可以避免幻讀的問題了,所以 MySQL 默認的隔離級別就是 RR 級別。
修改事務隔離級別
MySQL 中默認的 RR 級別,可以避免多事務的問題,所以一般不用修改,但在某些業務場景下也許需要修改隔離級別。
MySQL 命令行
使用命令:SET [GLOBAL|SESSION] TRANSACTION ISOLATION LEVEL {level 級別};
leve 級別:READ UNCOMMITTED,READ COMMITTED,REPEATABLE READ,SERIALIZABLE
Spring 事務註解
也可以使用 Spring 的註解 @Transactional 來修改事務級別, @Transactional 註解有一個參數 isolation 可以直接設置隔離級別,默認為 @Transactional(isolation=Isolation.DEFAULT),也就是説默認為 MySQL 設置的級別。
其它參數為:
Isolation.READ_UNCOMMITTED
Isolation.READ_COMMITTED
Isolation.REPEATABLE_READ
Isolation.SERIALIZABLE
undo log 版本鏈
MySQL 事務開始後會生成一個全局唯一的事務 ID,並且每一行數據其實都有兩個隱藏字段 trx_id(最近更新這條數據的事務 ID)和 roll_pointer(undo log 編號),所以如果一個事務更新某行數據時,會把自己的事務 ID 寫入這行數據的 trx_id,這行數據的 roll_pointer 會指向最近的 undo log,如此一來,這行數據在多事務場景下修改時,它的 undo log 就會形成一個 undo log 版本鏈作為這行數據在每個事務中的快照。
比如首先事務 A 插入一行數據值 A,同時生成 insert undo log,在事務 A 提交後,剛才生成的 undo log 就會直接被刪除。
緊接着事務 B 去修改這行數據為值 B,同時生成 update undo log,在事務 B 提交後,剛才生成的 undo log 不會立即刪除。
然後事務 C 把這行數據刪除了,但是刪除操作並不會直接刪除數據行,而是改變數據行中 delete 標記位,標記這行數據是刪除狀態。
等待 purge 線程在適當時機刪除這行數據的 undo log 版本鏈。
ReadView 機制
在瞭解 ReadView 之前,首先得知道 MySQL 讀操作有兩種方式:
當前讀:讀取的是數據最新版本,對當前讀取的數據加鎖,阻塞其它事務對數據行的修改,比如增刪改操作,或者 select * from table for update | insert | delete
快照讀:讀取的是數據歷史版本,通過 mvcc 機制讀取數據的歷史版本,比如普通的 Select 操作。
ReadView 機制是基於 undo log 版本鏈實現的。ReadView 保存當前系統內尚未提交的事務ID,需要説明的是,事務 ID 是 MySQL 按照時間順序生成的一個版本號。當前讀或者隔離級別為 RC 時,事務期間每次查詢都會重新生成一個 ReadView;而快照讀只會在事務開啟時生成一次 ReadView,後續事務操作都不會修改這個 ReadView。
ReadView 中包含 4 個主要的屬性:
m_ids:尚未提交的事務ID
min_trx_id:m_ids 中最小的值
max_trx_id:Mysql 下一個即將生成的ID
creator_trx_id:當前事務ID
通過 ReadView 生成機制可以知道,ReadView 中保存着當前事務開啟時,系統內所有未提交事務的ID快照。所有小於 min_trx_id 的事務 ID 都是已經提交的,這個需要明確;所以當前讀或 RC 級別下,每次查詢都能感知到哪些事務是已經提交的,哪些事務是本事務開啟時仍然未提交的,哪些事務是本事務開啟後再開啟的。通過時間軸可以更加清晰的瞭解 ReadView 對事務狀態的劃分:
m_ids 列表中有一個必然是本事務 ID creator_trx_id;而列表中第一個則是 min_trx_id;列表最後一個的下一個就是系統即將生成的事務 ID max_trx_id;
判斷機制規則:
if (trx_id <= min_trx_id) {
// 説明修改這行數據的事務已經提交,或者説就是本事務修改的,那麼該版本可讀。
} else if ( min_trx_id < trx_id && trx_id < max_trx_id) {
// 這時需要判斷修改這行數據的事務 ID trx_id 是否在 m_ids 列表中
if (m_ids.contains(trx_id)) {
// 説明生成 ReadView 時,該事務還未提交,那麼該版本不可讀
// 根據 undo log 版本鏈讀取最近的歷史版本
// 如果沒有合適的歷史版本,那麼這行數據就是不可讀的
} else {
// 説明生成 ReadView 時,該事務已提交,那麼該版本可讀。
}
} else {
// 説明修改這行數據的事務,在生成 ReadView 後才開啟的,那麼該版本不可讀
// 根據 undo log 版本鏈讀取最近的合適的歷史版本,
// 如果沒有合適的歷史版本,那麼這行數據就是不可讀的
}
這個機制就是事務在讀取時,會判斷這行數據的 trx_id 和事務本身 ReadView 中的 min_trx_id 的大小關係,以及 trx_id 是否在 m_ids 列表中,來決定這行數據是否可讀,以及用什麼方式來讀。ReadView 機制和 undo log 版本鍊形成了 MySQL 的 MVCC(Multi-Version Concurrent Control)機制(多版本併發控制機制)。
MySQL 行鎖
獨佔鎖:事務更新數據時會加獨佔鎖,加獨佔鎖的數據行其它事務不可修改,但是可以通過 mvcc 機制讀取數據行快照,其它事務必須等待當前事務提交才能修改。也可以使用 select * from table for update | insert | delete 在查詢時對數據行添加獨佔鎖,那麼其它事務此時就不能更新這行數據,也不能對這行數據加共享鎖。
共享鎖:事務查詢數據行時默認不會加鎖,但是可以使用 select * from table lock in share mode 添加共享鎖。
獨佔鎖和獨佔鎖之間是互斥的,意思就是在修改數據行時不允許其它事務修改,這也是避免髒寫的方法。
獨佔鎖和共享鎖之間是互斥的,意思就是在讀取數據行時不允許其它事務修改。
共享鎖和共享鎖之間是兼容的,意思就是事務之間讀取數據行都不會影響彼此,畢竟只是讀數據而已。
MySQL 表鎖
Innodb 存儲引擎提供表級鎖,在執行 DDL 語句時會添加表級鎖,此時會阻斷所有的增刪改操作;執行增刪改操作會阻塞 DDL 執行。
表級獨佔鎖:LOCK TABLES {table_name} WRITE
表級共享鎖:LOCK TABLES {table_name} READ
意向獨佔鎖:執行增刪改時,會自動在表上添加。
意向共享鎖:執行查詢操作時,會自動在表上添加。
所有的意向鎖之間都是兼容的,就是説所有的事務都可以在同一時間對錶內的數據進行增刪改查操作。
1. 意向獨佔鎖和表級獨佔鎖是互斥的,所以在執行 DDL 語句修改表時,會阻塞所有的增刪改操作;反之,執行增刪改操作會阻塞 DDL 執行。
2. 意向共享鎖和表級獨佔鎖是互斥的,所以在事務執行查詢操作時,會阻塞 DDL 執行;反之,在執行 DDL 語句時,會阻塞所有的查詢操作。
3. 表級共享鎖和表級獨佔鎖、意向獨佔鎖是互斥的,加上這個鎖就不允許對這個表的數據進行增刪改操作,只能讀數據。
4. 表級獨佔鎖與其它鎖都是互斥的,加上這個鎖就不允許對錶進行任何讀寫操作。
事務之間的增刪改查不會被意向鎖相互影響,但是增刪改查的意向鎖和表級獨佔鎖是互斥的,意思就是執行增刪改查時,不允許修改表;反之,修改表時,不允許增刪改查。
MVCC 機制
MVCC 機制是基於ReadView 機制和 undo log 版本鍊形成的。主要用來解決多事務間的髒讀、不可重複讀、幻讀等問題,使用無鎖機制提高數據庫的併發性能。
SQL 標準事務隔離機制有四個級別:RU、RC、RR、S 級。MVCC 在 RC、RR 級別中使用。
RU 級別
防止多事務間的髒寫,但是允許髒讀。也就是説不允許修改未提交事務修改的數據行,但是允許讀取未提交事務修改的數據行。
要實現這個級別,只需要在事務修改數據時添加獨佔鎖即可。此時其它修改數據行的事務會被阻塞,只允許讀取。
當事務執行完成後會喚醒等待的其它事務,並修改它們的鎖狀態為 false。
RC 級別
防止多事務間的髒讀,但是允許不可重複讀、幻讀。也就是説不允許讀取未提交事務修改的數據,這個級別事務讀取的數據行都是其它事務已經提交的。
這個級別的實現就需要使用 ReadView 機制和 undo log 版本鏈了,也就是 MVCC 機制。
首先事務每發起一次讀取都會重新生成一個 ReadView 記錄查詢時系統內尚未提交的事務 ID 快照,這個是非常重要的。
使用 ReadView 和 undo log 就已經達到防止髒讀的目的了,因為在發起讀取時,所有被修改的事務未提交的數據都不會被讀取到,只會讀取已提交的或者歷史版本。
然後本事務再次發起讀取,再生成一個新的 ReadView,此時剛才修改數據行的事務可能已經提交了,那麼這時再根據 ReadView 機制進行讀取,就可能讀取到剛才被修改的數據了。也就產生了不可重複讀的問題。如果剛才那個事務是添加操作,那麼根據上述讀取規則就會產生幻讀的問題。
RR 級別
防止多事務間的不可重複讀和幻讀(只針對快照讀,當前讀需要加鎖),也就是説事務開啟後讀取的數據其實就是一個快照版本,無論其它事務對數據的增刪改,是否提交,都不會影響本事務對數據的查詢。這個級別的實現也是需要 MVCC 機制實現的。
與 RC 級別不同的是,這個級別的事務在開啟後首次查詢時會生成一個 ReadView,後續都不會對其修改,也就是説這個事務所有的讀取操作都會基於這個 ReadView 記錄的事務ID快照進行讀取。就這個 ReadView 生成的區別就會讓 RR 級別的事務不會產生不可重複讀和幻讀的問題,因為事務期間所有讀取操作都是基於同一個 ReadView。還有就是如果數據行是 ReadView 生成後其它事務新增的,那麼這行數據是不會有合適的歷史版本的。
綜上,RC、RR 主要區別就是 ReadView 生成的規則,就是這個規則才會使 RR 級防止幻讀和不可重複讀的出現。當然,上述的讀,都是基於快照讀而言的。
S 級別
加鎖,使所有事務串行化,可以避免所有的多事務問題。而後果也是可想而知的,將會導致 MySQL 性能及其低下。
挺不錯的 建議收藏 或關注專欄 持續整理相關知識