理解完這些基本上能解決面試中MySql的事務問題

越努力,越幸運,
本文已收藏在GitHub中 JavaCommunity, 裏面有面試分享、源碼分析系列文章,歡迎收藏,點贊
https://github.com/Ccww-lx/Ja...

前言

在面試中,基本上都會問到關於數據庫的事務問題,若是啥都不會或者只回答到表面的上知識點的話,那面試基本上是沒戲了,爲了能順利經過面試,那MySql的事務問題就須要瞭解,因此就根據網上的資料總結一版Mysql事務的知識點,鞏固一下事務的知識。git

事務

事務是指邏輯上的一組操做,要麼都執行,要麼都不執行,github

事務的特性(ACID)

  • 原子性(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( 回滾日誌 )

undo log (回滾日誌):是採用段(segment)的方式來記錄的,每一個undo操做在記錄的時候佔用一個undo log segment。爲何會在數據更改操做的時候,記錄了相對應的undo log呢?其目的在於:源碼分析

  • 爲了保證數據的原子性,記錄事務發生以前的一個版本,用於回滾,
  • 經過mvcc+undo log實現innodb事務可重複讀和讀取已提交隔離級別。

其中,undo log分爲:

  • insert undo log insert操做中產生的undo log
  • update undo log: 對deleteupdate操做產生的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>>,分別爲:

  • 讀未提交(髒讀):容許讀取還沒有提交的數據,容許髒讀
  • 讀已提交( 不可重複讀 ):容許讀取事務已經提交的數據
  • 可重複讀( 幻讀 ):在同一個事務內的查詢結果都是和事務開始時刻查詢一致的( InnoDB默認級別 )
  • 串行化:全部事務逐個依次執行, 每次讀都須要得到表級共享鎖,讀寫相互都會阻塞

其中,<font color='red'>不一樣的隔離級別可能會存在在不一樣併發問題</font>>,主要併發問題包括:

  • 數據丟失: 兩個或多個事務操做相同數據,基於最初選定的值更新該行時,因爲每一個事務都不知道其餘事務的存在,就會發生丟失更新問題——最後的更新覆蓋了其餘事務所作的更新
  • 髒讀:讀到了其餘事務還未提交的數據,事務A讀取了事務B更新的數據,而後B回滾操做,那麼A讀取到的數據是髒數據

  • 不可重複讀(重點是修改):在一個事務中,前後進行兩次相同的讀取,因爲另外一個事務修改了數據,致使先後兩次結果的不一致,事務A屢次讀取同一數據,事務B在事務A屢次讀取的過程當中,對數據做了更新並提交,致使事務A屢次讀取同一數據時,結果不一致。

  • 幻讀(重點是新增、刪除): 在一個事務中,前後進行兩次相同的讀取(通常是範圍查詢),因爲另外一個事務新增或刪除了數據,致使先後兩次結果不一致

不可重複讀和幻讀的區別?

不可重複讀和幻讀最大的區別,就在於如何經過鎖機制來解決他們產生的問題,

使用鎖機制來實現這兩種隔離級別,在可重複讀中,相同sql第一次讀取到數據後就將這些數據加鎖,其它事務沒法更新操做這些數據來實現可重複讀了隔離。

但這種處理方式卻沒法鎖住insert的數據,所以會出現當事務A先前讀取了數據,事務B再insert數據提交,結果發現事務A就會發現莫名其妙多了些數據,這就是幻讀,不能經過行鎖來避免 。

瞭解了併發問題後,來看看不一樣的隔離級別可能會存在在不一樣併發問題:

事務隔離級別 髒讀 不可重複讀 幻讀
讀未提交
不可重複讀
可重複讀
串行化

爲了實現事務隔離,延伸出了數據庫鎖。其中,<font color='red'>innodb事務的隔離級別是由鎖機制和MVCC(多版本併發控制)來實現的</font>

那咱們來先看看鎖的原理,怎麼使用鎖來實現事務隔離的呢?

鎖機制

鎖機制的基本工做原理,事務在修改數據以前,須要先得到相應的鎖;得到鎖以後,事務即可以修改數據;該事務操做期間,這部分數據是鎖定的,其餘事務若是須要修改數據,須要等待當前事務提交或回滾後釋放鎖,

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 多版本併發控制)

InnoDBMVCC是經過在每行記錄後面保存兩個隱藏的列來實現的,一個保存了行的事務ID(事務ID就會遞增 )一個保存了行的回滾段的指針

每開始一個新的事務,都會自動遞增產 生一個新的事務id。事務開始時刻的會把事務id放到當前事務影響的行事務id中,而DB_ROLL_PTR表示指向該行回滾段的指針,該行記錄上全部版本數據,在undo中都經過鏈表形式組織,該值實際指向undo中該行的歷史記錄鏈表,

<font color='red'>在併發訪問數據庫時,對正在事務中的數據作MVCC多版本的管理,以免寫操做阻塞讀操做,而且會經過比較版本解決幻讀</font>。

並且MVCC只在REPEATABLE READREAD 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具體是如何操做的。

RR隔離級別下,MVCC具體操做

SELECT操做,InnoDB遵循之後兩個規則執行:

  1. <font color='red'>InnoDB只查找版本早於當前事務版本的數據行(即行的事務編號小於或等於當前事務的事務編號)</font>,這樣能夠確保事務讀取的行,要麼是在事務開始前已經存在的,要麼是事務自身插入或者修改過的記錄。
  2. <font color='red'>行的刪除版本要麼未定義,讀取到事務開始以前狀態的版本</font>>,這能夠確保事務讀取到的行,在事務開始以前未被刪除.只有同時知足的二者的記錄,才能返回做爲查詢結果.

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如何實現的呢?

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技術博客》觀看更多文章,也歡迎關注一波。
image.png
相關文章
相關標籤/搜索