越努力,越幸運,
本文已收藏在GitHub中JavaCommunity, 里面有面試分享、原始碼分析系列文章,歡迎收藏,點贊
https://github.com/Ccww-lx/JavaCommunity
前言
在面試中,基本上都會問到關于資料庫的事務問題,如果啥都不會或者只回答到表面的上知識點的話,那面試基本上是沒戲了,為了能順利通過面試,那MySql的事務問題就需要了解,所以就根據網上的資料總結一版Mysql事務的知識點,鞏固一下事務的知識,
事務
事務是指邏輯上的一組操作,要么都執行,要么都不執行,
事務的特性(ACID)
-
原子性(
Atomicity):事務是不可分割的作業單元,要么都成功,要么都失敗, 如果事務中一個sql陳述句執行失敗,則已執行的陳述句也必須回滾,資料庫退回到事務前的狀態, -
一致性(
Consistency):事務不能破壞資料的完整性和業務的一致性 ,例如在銀行轉賬時,不管事務成功還是失敗,雙方錢的總額不變 -
隔離性(
Isolation):一個事務所操作的資料在提交之前,對其他事務的可見性設定(一般是不可見) -
持久性(
Durability):事務提交之后,所做的修改就會永久保存,不會因為系統故障導致資料丟失
嚴格來說,只有同時滿足資料庫的事務ACID特性才能算一個完整的事務,但現實中實作能夠真正滿足的完整的事務特性少之又少,但是在實作中也必須盡量達到事務要求的特性,
那么事務ACID特性具體怎么實作的呢?我們來分析看看,首先先看看事務的特性,
原子性(Atomicity)
首先我們來看看事務的原子性特性,看看其如何實作的?
原子性(Atomicity):事務是不可分割的作業單元,要么都成功,要么都失敗, 如果事務中一個sql陳述句執行失敗,則已執行的陳述句也必須回滾,資料庫退回到事務前的狀態
原子性(Atomicity)的實作離不開 MySQL的事務日志 undo log日志型別,當事務需要回滾的時候需要將資料庫狀態回滾到事務開始前,即需要撤銷所有已經成功執行的sql陳述句,那么undo log起了關鍵性作用:
當事務對資料庫進行修改時,InnoDB會生成對應的undo log;如果事務執行失敗或呼叫了rollback,導致事務需要回滾,便可以利用undo log中的資訊將資料回滾到修改之前的樣子,
那么undo log是什么呢?每個資料變更操作是怎么被記錄下來的呢?
undo log( 回滾日志 )
undo log (回滾日志):是采用段(segment)的方式來記錄的,每個undo操作在記錄的時候占用一個undo log segment,為什么會在資料更改操作的時候,記錄了相對應的undo log呢?其目的在于:
- 為了保證資料的原子性,記錄事務發生之前的一個版本,用于回滾,
- 通過
mvcc+undo log實作innodb事務可重復讀和讀取已提交隔離級別,
其中,undo log分為:
insert undo log:insert操作中產生的undo log,update undo log: 對delete和update操作產生的undo log
資料更改的undo log怎么記錄的呢?
因為insert操作的記錄,只對事務本身可見,對其他事務不可見,故該undo log可以在事務提交后直接洗掉,不需要進行purge操作,
而Delete操作在事務中實際上并不是真正的洗掉掉資料行,而是一種Delete Mark操作,在記錄上標識Delete_Bit,而不洗掉記錄,是一種"假洗掉",只是做了個標記,真正的洗掉作業需要后臺purge執行緒去完成,
update分為兩種情況:update的列是否是主鍵列,
- 如果不是主鍵列,在
undo log中直接反向記錄是如何update的,即update是直接進行的, - 如果是主鍵列,
update分兩部執行:先洗掉該行,再插入一行目標行,
與insert undo log不同的,update undo log日志,當事務提交的時候,innodb不會立即洗掉undo log, 會將該事務對應的undo log放入到洗掉串列中,未來通過purge執行緒來洗掉,
因為后續還可能會用到undo log,如隔離級別為repeatable read時,事務讀取的都是開啟事務時的最新提交行版本,只要該事務不結束,該行版本就不能洗掉(即undo log不能洗掉),且undo log分配的頁可重用減少存盤空間和提升性能,
Note: purge執行緒兩個主要作用是:清理undo頁和清除page里面帶有Delete_Bit標識的資料行,
接著我們來看看事務的隔離性,看看事務有哪些隔離級別,而且事務并發中會產生什么問題,
隔離性(Isolation)
隔離性(Isolation),是指事務內部的操作與其他事務是隔離的,并發執行的各個事務之間不能互相干擾 ,一個事務所操作的資料在提交之前,對其他事務的可見性設定(一般是不可見),
事務隔離級別
而且資料庫為了在并發下有效保證讀取資料正確性,資料庫提供了四種事務隔離級別>,分別為:
- 讀未提交(臟讀):允許讀取尚未提交的資料,允許臟讀
- 讀已提交( 不可重復讀 ):允許讀取事務已經提交的資料
- 可重復讀( 幻讀 ):在同一個事務內的查詢結果都是和事務開始時刻查詢一致的( InnoDB默認級別 )
- 串行化:所有事務逐個依次執行, 每次讀都需要獲得表級共享鎖,讀寫相互都會阻塞
其中,不同的隔離級別可能會存在在不同并發問題>,主要并發問題包括:
- 資料丟失: 兩個或多個事務操作相同資料,基于最初選定的值更新該行時,由于每個事務都不知道其他事務的存在,就會發生丟失更新問題——最后的更新覆寫了其他事務所做的更新
- **臟讀:**讀到了其他事務還未提交的資料,事務A讀取了事務B更新的資料,然后B回滾操作,那么A讀取到的資料是臟資料

- **不可重復讀(重點是修改):**在一個事務中,先后進行兩次相同的讀取,由于另一個事務修改了資料,導致前后兩次結果的不一致,事務A多次讀取同一資料,事務B在事務A多次讀取的程序中,對資料作了更新并提交,導致事務A多次讀取同一資料時,結果不一致,

-
幻讀(重點是新增、洗掉): 在一個事務中,先后進行兩次相同的讀取(一般是范圍查詢),由于另一個事務新增或洗掉了資料,導致前后兩次結果不一致

不可重復讀和幻讀的區別?
不可重復讀和幻讀最大的區別,就在于如何通過鎖機制來解決他們產生的問題,
使用鎖機制來實作這兩種隔離級別,在可重復讀中,相同sql第一次讀取到資料后就將這些資料加鎖,其它事務無法更新操作這些資料來實作可重復讀了隔離,
但這種處理方式卻無法鎖住insert的資料,因此會出現當事務A先前讀取了資料,事務B再
insert資料提交,結果發現事務A就會發現莫名其妙多了些資料,這就是幻讀,不能通過行鎖來避免 ,
了解了并發問題后,來看看不同的隔離級別可能會存在在不同并發問題:
| 事務隔離級別 | 臟讀 | 不可重復讀 | 幻讀 |
|---|---|---|---|
| 讀未提交 | 是 | 是 | 是 |
| 不可重復讀 | 否 | 是 | 是 |
| 可重復讀 | 否 | 否 | 是 |
| 串行化 | 否 | 否 | 否 |
為了實作事務隔離,延伸出了資料庫鎖,其中,innodb事務的隔離級別是由鎖機制和MVCC(多版本并發控制)來實作的
那我們來先看看鎖的原理,怎么使用鎖來實作事務隔離的呢?
鎖機制
鎖機制的基本作業原理,事務在修改資料之前,需要先獲得相應的鎖;獲得鎖之后,事務便可以修改資料;該事務操作期間,這部分資料是鎖定的,其他事務如果需要修改資料,需要等待當前事務提交或回滾后釋放鎖,
MySQL主要分成三種型別(級別)的鎖機制:
-
表級鎖:最大顆粒度的鎖機制,鎖定資源爭用的概率也會最高 ,并發度最低 ,但開銷小,加鎖快,不會出現死鎖,
-
行級鎖:最大顆粒度的鎖機制很小, 發生鎖定資源爭用的概率也最小,能夠給予應用程式盡可能大的并發處理能力而提高一些需要高并發應用系統的整體性能 ,但 開銷大,加鎖慢;會出現死鎖 ,
-
頁級鎖: 開銷和加鎖時間界于表鎖和行鎖之間;會出現死鎖;鎖定粒度界于表鎖和行鎖之間,并發度一般
而且不同的存盤引擎支持不同的的鎖機制,主要分析一下InnoDB鎖,
InnoDB鎖
InnoDB實作了以下兩種型別的行鎖
- 共享鎖(S鎖、行鎖):多個事務對同一資料行可以共享一把鎖,只能讀不能修改
- 排它鎖(X鎖、行鎖):一個事務獲取一個資料行的排它鎖,那么其他事務將不能再獲取該行的鎖(共享鎖、排它鎖), 允許獲取排他鎖的事務更新資料
對于UPDATE,DELETE,INSERT操作, InnoDB會自動給涉及及資料集加排他鎖(X);對于普通SELECT陳述句,InnoDB不會加任何鎖,
而且因為InnoDB引擎允許行鎖和表鎖共存,實作多粒度鎖機制,使用意向鎖實作表鎖機制,
- 意向共享鎖(IS鎖、表鎖):當事務準備給資料行加共享鎖時,會先給表加上一個意向共享鎖,意向共享鎖之間是兼容的
- 意向排它鎖(IX鎖、表鎖):當事務準備給資料行加排它鎖時,會先給表加上一個意向排它鎖,意向排它鎖之間是兼容的
意向鎖(IS、IX)是InnoDB資料操作之前自動加的,不需要用戶干預,它的意義在于:當事務想去進行鎖表時,可以先判斷意向鎖是否存在,存在時則可快速回傳該表不能啟用表鎖,否則就需要等待,
其中,四種鎖的兼容性如下
| 當前鎖模式/是否兼容/請求鎖模式 | X | IX | S | IS |
|---|---|---|---|---|
| X | 沖突 | 沖突 | 沖突 | 沖突 |
| IX | 沖突 | 兼容 | 沖突 | 兼容 |
| S | 沖突 | 沖突 | 兼容 | 兼容 |
| IS | 沖突 | 兼容 | 兼容 | 兼容 |
如果一個事務請求的鎖模式與當前的鎖兼容,InnoDB就請求的鎖授予該事務;反之,如果兩者兩者不兼容,該事務就要等待鎖釋放,
InnoDB行鎖
InnoDB的行鎖是通過給索引上的索引項加鎖來實作的,只有通過索引檢索資料,才能使用行鎖,否則將使用表鎖(鎖住索引的所有記錄)
臨鍵鎖(next-key),可以防止幻讀,根據索引,劃分為一個個左開右閉的區間,當進行范圍查詢的時候,若命中索引且能夠檢索到資料,則鎖住記錄所在的區間和它的下一個區間,
其實,臨鍵鎖(Next-Key)=記錄鎖(Record Locks)+間隙鎖(Gap Locks),
- 當我們用范圍條件檢索資料而不是相等條件檢索資料,并請求共享或排他鎖時,InnoDB會給符合范圍條件的已有資料記錄的索引項加鎖;對于鍵值在條件范圍內但并不存在的記錄,叫做間隙(GAP),
- 當使用唯一索引,且記錄存在的精準查詢時,使用Record Locks記錄鎖
具體的使用體現在哪里呢?如下圖所示:
- 范圍查詢,記錄存在

- 當記錄不存在(不論是等值查詢,還是范圍查詢)時,next-key將退化成Gap Lock(間隙鎖)

- 當條件是精準匹配(即為等值查詢時)且記錄存在時,并且是唯一索引,臨鍵鎖(Next-Key)退化成Record Lock(記錄鎖)

- 當條件是精準匹配(即為等值查詢時)且記錄存在,但不是唯一索引時,臨鍵鎖(Next-Key)會有精準值的資料會增加Record Lock(記錄鎖)和精準值前后的區間的資料會增加Gap Lock(間隙鎖),

如何使用鎖解決并發問題
利用鎖解決臟讀、不可重復讀、幻讀
-
X鎖解決臟讀
-
S鎖解決不可重復讀
-
臨鍵鎖解決幻讀
Multiversion concurrency control (MVCC 多版本并發控制)
InnoDB的MVCC是通過在每行記錄后面保存兩個隱藏的列來實作的,一個保存了行的事務ID(事務ID就會遞增 ),一個保存了行的回滾段的指標 ,

每開始一個新的事務,都會自動遞增產 生一個新的事務id,事務開始時刻的會把事務id放到當前事務影響的行事務id中,而DB_ROLL_PTR表示指向該行回滾段的指標,該行記錄上所有版本資料,在undo中都通過鏈表形式組織,該值實際指向undo中該行的歷史記錄鏈表,
在并發訪問資料庫時,對正在事務中的資料做MVCC多版本的管理,以避免寫操作阻塞讀操作,并且會通過比較版本解決幻讀,
而且MVCC只在REPEATABLE READ和READ COMMITIED兩個隔離級別下才會作業,其中,MVCC實作實質就是保存資料在某個時間點的快照來實作的, 那哪些操作是快照讀?
快照讀和當前讀
快照讀,innodb快照讀,資料的讀取將由 cache(原本資料) + undo(事務修改前的資料) 兩部分組成
- 普通的
select,比如select * from table where ?;
當前讀,SQL讀取的資料是最新版本,通過鎖機制來保證讀取的資料無法通過其他事務進行修改
-
UPDATE -
DELETE -
INSERT -
SELECT … LOCK IN SHARE MODE -
SELECT … FOR UPDATE其中當前讀中,只有
SELECT … LOCK IN SHARE MODE對讀取記錄加S鎖 (共享鎖)外,其他的操作,都加的是X鎖 (排它鎖),
那么在RR隔離級別下,MVCC具體是如何操作的,
RR隔離級別下,MVCC具體操作
SELECT操作,InnoDB遵循以后兩個規則執行:
- InnoDB只查找版本早于當前事務版本的資料行(即行的事務編號小于或等于當前事務的事務編號),這樣可以確保事務讀取的行,要么是在事務開始前已經存在的,要么是事務自身插入或者修改過的記錄,
- 行的洗掉版本要么未定義,讀取到事務開始之前狀態的版本>,這可以確保事務讀取到的行,在事務開始之前未被洗掉.只有同時滿足的兩者的記錄,才能回傳作為查詢結果.
INSERT:InnoDB為新插入的每一行保存當前事務編號作為行版本號,
DELETE:InnoDB為洗掉的每一行保存當前事務編號作為行洗掉標識,
UPDATE:InnoDB為插入一行新記錄,保存當前事務編號作為行版本號,同時保存當前事務編號到原來的行作為行洗掉標識>,
保存這兩個額外系統版本號,使大多數讀操作都可以不用加鎖,這樣設計使得讀資料操作很簡單,性能很好,并且也能保證只會讀取到符合標準的行,不足之處是每行記錄都需要額外的存盤空間,需要做更多的行檢查作業,以及一些額外的維護作業,
分析完了原子性和隔離性,我們繼續看看事務的持久性,
持久性(Durability)
持久性(Durability):事務提交之后,所做的修改就會永久保存,不會因為系統故障導致資料丟失,
而且其實作的關鍵在于redo log, 在執行SQL時會保存已執行的SQL陳述句到一個指定的Log檔案,當執行recovery時重新執行redo log記錄的SQL操作,
那么redo log如何實作的呢?
redo log
當向資料庫寫入資料時,執行程序會首先寫入Buffer Pool,Buffer Pool中修改的資料會定期重繪到磁盤中(這一程序稱為刷臟),這整一程序稱為redo log,redo log 分為:
- Buffer Pool記憶體中的日志緩沖(redo log buffer),該部分日志是易失性的;
- 磁盤上的重做日志檔案(redo log file),該部分日志是持久的,
Buffer Pool的使用可以大大提高了讀寫資料的效率,但是也帶了新的問題:如果MySQL宕機,而此時Buffer Pool中修改的資料在記憶體還沒有重繪到磁盤,就會導致資料的丟失,事務的持久性無法保證,
為了確保事務的持久性,在當事務提交時,會呼叫fsync介面對redo log進行刷盤, (即redo log buffer寫日志到磁盤的redo log file中 ),重繪頻率由 innodb_flush_log_at_trx_commit變數來控制的:
- 0 : 每秒重繪寫入到磁盤中的,當系統崩潰,會丟失1秒鐘的資料 ;
- 1: 事務每次提交都寫入磁盤;
- 2:每秒重繪寫入到磁盤中的,但跟0是有區別的,
redo log有更加詳細的解讀,后續有時間再補上,到現在為止,已經將事務三個特性都理解了,那事務一致性呢?
一致性(Consistency)
一致性(Consistency):事務不能破壞資料的完整性和業務的一致性 :
-
資料的完整性: 物體完整性、列完整性(如欄位的型別、大小、長度要符合要求)、外鍵約束等
-
業務的一致性:例如在銀行轉賬時,不管事務成功還是失敗,雙方錢的總額不變,
那是如何保證資料一致性的?
其實資料一致性是通過事務的原子性、持久性和隔離性來保證的
- 原子性:陳述句要么全執行,要么全不執行,是事務最核心的特性,事務本身就是以原子性來定義的;實作主要基于undo log
- 持久性:保證事務提交后不會因為宕機等原因導致資料丟失;實作主要基于redo log
- 隔離性:保證事務執行盡可能不受其他事務影響;InnoDB默認的隔離級別是RR,RR的實作主要基于鎖機制(包含next-key lock)、MVCC(包括資料的隱藏列、基于undo log的版本鏈、ReadView)
總結
其中要同時滿足ACID特性,這樣的事務少之又少,實際中很多例子都只是滿足一些特性,比如:
- MySQL的NDB Cluster事務不滿足持久性和隔離性;
- InnoDB默認事務隔離級別是可重復讀,不滿足隔離性;
- Oracle默認的事務隔離級別為READ COMMITTED,不滿足隔離性
所以我們只能使用這個四個維度的特性去衡量事務的操作,
謝謝各位點贊,沒點贊的點個贊支持支持
最后,微信搜《Ccww技術博客》可觀看更多文章
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/177599.html
標籤:其他
