越努力,越幸運,
本文已收藏在GitHub中 JavaCommunity, 裏面有面試分享、源碼分析系列文章,歡迎收藏,點贊
https://github.com/Ccww-lx/Ja...
在面試中,基本上都會問到關於數據庫的事務問題,若是啥都不會或者只回答到表面的上知識點的話,那面試基本上是沒戲了,爲了能順利經過面試,那MySql的事務問題就須要瞭解,因此就根據網上的資料總結一版Mysql事務的知識點,鞏固一下事務的知識。git
事務是指邏輯上的一組操做,要麼都執行,要麼都不執行,github
Atomicity
):事務是不可分割的工做單元,要麼都成功,要麼都失敗, 若是事務中一個sql語句執行失敗,則已執行的語句也必須回滾,數據庫退回到事務前的狀態。Consistency
):事務不能破壞數據的完整性和業務的一致性 。例如在銀行轉帳時,無論事務成功仍是失敗,雙方錢的總額不變Isolation
):一個事務所操做的數據在提交以前,對其餘事務的可見性設定(通常是不可見)Durability
):事務提交以後,所作的修改就會永久保存,不會由於系統故障致使數據丟失嚴格來講,只有同時知足數據庫的事務ACID特性才能算一個完整的事務,但現實中實現可以真正知足的完整的事務特性少之又少,可是在實現中也必須儘可能達到事務要求的特性。面試
那麼事務ACID特性具體怎麼實現的呢?咱們來分析看看,首先先看看事務的特性。sql
Atomicity
)首先咱們來看看事務的原子性特性,看看其如何實現的?數據庫
原子性(Atomicity
):事務是不可分割的工做單元,要麼都成功,要麼都失敗, 若是事務中一個sql語句執行失敗,則已執行的語句也必須回滾,數據庫退回到事務前的狀態微信
原子性(Atomicity
)的實現離不開 MySQL的事務日誌 undo log
日誌類型,當事務須要回滾的時候須要將數據庫狀態回滾到事務開始前,即須要撤銷全部已經成功執行的sql語句。那麼undo log
起了關鍵性做用:併發
<font color='red'>當事務對數據庫進行修改時,InnoDB會生成對應的undo log
;若是事務執行失敗或調用了rollback
,致使事務須要回滾,即可以利用undo log
中的信息將數據回滾到修改以前的樣子。</font>mvc
那麼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
),是指事務內部的操做與其餘事務是隔離的,併發執行的各個事務之間不能互相干擾 ,一個事務所操做的數據在提交以前,對其餘事務的可見性設定(通常是不可見)。
並且數據庫爲了在併發下有效保證讀取數據正確性,數據庫提供了<font color='red'>四種事務隔離級別</font>>,分別爲:
其中,<font color='red'>不一樣的隔離級別可能會存在在不一樣併發問題</font>>,主要併發問題包括:
不可重複讀和幻讀的區別?不可重複讀和幻讀最大的區別,就在於如何經過鎖機制來解決他們產生的問題,
使用鎖機制來實現這兩種隔離級別,在可重複讀中,相同sql第一次讀取到數據後就將這些數據加鎖,其它事務沒法更新操做這些數據來實現可重複讀了隔離。
但這種處理方式卻沒法鎖住insert的數據,所以會出現當事務A先前讀取了數據,事務B再
insert
數據提交,結果發現事務A就會發現莫名其妙多了些數據,這就是幻讀,不能經過行鎖來避免 。
瞭解了併發問題後,來看看不一樣的隔離級別可能會存在在不一樣併發問題:
事務隔離級別 | 髒讀 | 不可重複讀 | 幻讀 |
---|---|---|---|
讀未提交 | 是 | 是 | 是 |
不可重複讀 | 否 | 是 | 是 |
可重複讀 | 否 | 否 | 是 |
串行化 | 否 | 否 | 否 |
爲了實現事務隔離,延伸出了數據庫鎖。其中,<font color='red'>innodb事務的隔離級別是由鎖機制和MVCC(多版本併發控制)來實現的</font>
那咱們來先看看鎖的原理,怎麼使用鎖來實現事務隔離的呢?
鎖機制的基本工做原理,事務在修改數據以前,須要先得到相應的鎖;得到鎖以後,事務即可以修改數據;該事務操做期間,這部分數據是鎖定的,其餘事務若是須要修改數據,須要等待當前事務提交或回滾後釋放鎖,
MySQL主要分紅三種類型(級別)的鎖機制:
並且不一樣的存儲引擎支持不一樣的的鎖機制,主要分析一下InnoDB鎖。
InnoDB實現瞭如下兩種類型的行鎖
對於UPDATE
,DELETE
,INSERT
操做, InnoDB會自動給涉及及數據集加排他鎖(X);對於普通SELECT語句,InnoDB不會加任何鎖,
並且由於InnoDB引擎容許行鎖和表鎖共存,實現多粒度鎖機制,使用意向鎖實現表鎖機制,
意向鎖(IS、IX)是InnoDB數據操做以前自動加的,不須要用戶干預。它的意義在於:當事務想去進行鎖表時,能夠先判斷意向鎖是否存在,存在時則可快速返回該表不能啓用表鎖,不然就須要等待,
其中,四種鎖的兼容性以下
當前鎖模式/是否兼容/請求鎖模式 | X | IX | S | IS |
---|---|---|---|---|
X | 衝突 | 衝突 | 衝突 | 衝突 |
IX | 衝突 | 兼容 | 衝突 | 兼容 |
S | 衝突 | 衝突 | 兼容 | 兼容 |
IS | 衝突 | 兼容 | 兼容 | 兼容 |
若是一個事務請求的鎖模式與當前的鎖兼容,InnoDB就請求的鎖授予該事務;反之,若是二者二者不兼容,該事務就要等待鎖釋放。
InnoDB的行鎖是經過給索引上的索引項加鎖來實現的。只有經過索引檢索數據,才能使用行鎖,不然將使用表鎖(鎖住索引的全部記錄)
臨鍵鎖(next-key)
,能夠防止幻讀。根據索引,劃分爲一個個左開右閉的區間。當進行範圍查詢的時候,若命中索引且可以檢索到數據,則鎖住記錄所在的區間和它的下一個區間,
其實,臨鍵鎖(Next-Key)=記錄鎖(Record Locks)+間隙鎖(Gap Locks),
具體的使用體如今哪裏呢?以下圖所示:
利用鎖解決髒讀、不可重複讀、幻讀
Multiversion concurrency control
(MVCC
多版本併發控制)InnoDB
的MVCC
是經過在每行記錄後面保存兩個隱藏的列來實現的,一個保存了行的事務ID(事務ID就會遞增 ),一個保存了行的回滾段的指針 。
每開始一個新的事務,都會自動遞增產 生一個新的事務id。事務開始時刻的會把事務id放到當前事務影響的行事務id中,而DB_ROLL_PTR
表示指向該行回滾段的指針,該行記錄上全部版本數據,在undo中都經過鏈表形式組織,該值實際指向undo中該行的歷史記錄鏈表,
<font color='red'>在併發訪問數據庫時,對正在事務中的數據作MVCC多版本的管理,以免寫操做阻塞讀操做,而且會經過比較版本解決幻讀</font>。
並且MVCC只在REPEATABLE READ
和READ COMMITIED
兩個隔離級別下才會工做,其中,MVCC實現實質就是保存數據在某個時間點的<font color='red'>快照</font>來實現的。 那哪些操做是快照讀?
快照讀,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具體是如何操做的。
SELECT操做,InnoDB遵循之後兩個規則執行:
INSERT:<font color='red'>InnoDB爲新插入的每一行保存當前事務編號做爲行版本號</font>。
DELETE:<font color='red'>InnoDB爲刪除的每一行保存當前事務編號做爲行刪除標識</font>。
UPDATE:<font color='red'>InnoDB爲插入一行新記錄,保存當前事務編號做爲行版本號,同時保存當前事務編號到原來的行做爲行刪除標識</font>>。
保存這兩個額外系統版本號,使大多數讀操做均可以不用加鎖。這樣設計使得讀數據操做很簡單,性能很好,而且也能保證只會讀取到符合標準的行,不足之處是每行記錄都須要額外的存儲空間,須要作更多的行檢查工做,以及一些額外的維護工做。
分析完了原子性和隔離性,咱們繼續看看事務的持久性。
Durability
)持久性(Durability
):事務提交以後,所作的修改就會永久保存,不會由於系統故障致使數據丟失,
並且其實現的關鍵在於redo log
, 在執行SQL時會保存已執行的SQL語句到一個指定的Log文件,當執行recovery
時從新執行redo log
記錄的SQL操做。
那麼redo log
如何實現的呢?
當向數據庫寫入數據時,執行過程會首先寫入Buffer Pool,Buffer Pool中修改的數據會按期刷新到磁盤中(這一過程稱爲刷髒),這整一過程稱爲redo log。redo log 分爲:
Buffer Pool的使用能夠大大提升了讀寫數據的效率,可是也帶了新的問題:若是MySQL宕機,而此時Buffer Pool中修改的數據在內存尚未刷新到磁盤,就會致使數據的丟失,事務的持久性沒法保證。
爲了確保事務的持久性,在當事務提交時,會調用fsync
接口對redo log
進行刷盤, (即redo log buffer
寫日誌到磁盤的redo log file
中 ),刷新頻率由 innodb_flush_log_at_trx_commit
變量來控制的:
redo log有更加詳細的解讀,後續有時間再補上,到如今爲止,已經將事務三個特性都理解了,那事務一致性呢?
Consistency
)一致性(Consistency
):事務不能破壞數據的完整性和業務的一致性 :
那是如何保證數據一致性的?
其實數據一致性是經過事務的原子性、持久性和隔離性來保證的
其中要同時知足ACID特性,這樣的事務少之又少。實際中不少例子都只是知足一些特性,好比:
因此咱們只能使用這個四個維度的特性去衡量事務的操做。
謝謝各位點贊,沒點讚的點個贊支持支持
最後,微信搜《Ccww技術博客》觀看更多文章,也歡迎關注一波。
![]()