面試上來就問的MySQL事務

一、什麼是事務

事務是獨立的工作單元,在這個獨立工作單元中所有操作要麼全部成功,要麼全部失敗。

也就是說如果有任何一條語句因為崩潰或者其它原因導致執行失敗,那麼未執行的語句都不會再執行,已經執行的語句會進行回滾操作,這個過程被稱之為事務。

例:

最近在寫一個論壇系統,當發佈的主題被其它用戶舉報後,後臺會對舉報內容進行審核。

一經審核為違規主題,則進行刪除主題的操作,但不僅僅要刪除主題還要刪除主題下的帖子、瀏覽量,關於這個主題的一切信息都需要進行清理。

刪除流程如下,用上邊概念來說,以下執行的四個流程,每個流程都必須成功否則事務回滾返回刪除失敗。

假設執行到了第三步後SQL執行失敗了,那麼第一二步都會進行回滾,第四步則不會再執行。

面試上來就問的MySQL事務

二、事務四大特徵

事務的四大特徵,原子性、一致性、隔離性、持久性。

1. 原子性

事務中所有操作要麼全部成功,要麼全部失敗,不會存在一部分成功,一部分失敗。

這個概念也是事務最核心的特性,事務概念本身就是使用原子性進行定義的。

原子性的實現是基於回滾日誌實現(undo log),當事務需要回滾時就會調用回滾日誌進行SQL語句回滾操作,實現數據還原。

2. 一致性

一致性,字面意思就是前後一致唄!在數據庫中不管進行任何操作,都是從一個一致性轉移到另一個一致性。

當事務結束後,數據庫的完整性約束不被破壞。

當你瞭解完事務的四大特徵之後就會發現,都是保證數據一致性為最終目標存在的。

在學習事務的過程中大家看到最多的案例就是轉賬,假設用戶A與用戶B餘額共計1000,那麼不管怎麼轉倆人的餘額自始至終也就只有1000。

3. 隔離性

保證事務執行儘可能的不受其它事務影響,這個是隔離級別可以自行設置,在innodb中默認的隔離級別為可重複讀(Repeatable Read)。

這種隔離級別有可能造成的問題就是出現幻讀,但是使用間隙鎖可以解決幻讀問題。

學習了隔離性你需要知道原子性和持久性是針對單個事務,而隔離性是針對事務與事務之間的關係。

4. 持久性

持久性是指當事務提交之後,數據的狀態就是永久的,不會因為系統崩潰而丟失。

事務持久性是基於重做日誌(redo log)實現的。

三、事務併發會出現的問題

1. 髒讀

讀取了另一個事務沒有提交的數據。

事務A

事務B

執行事務

執行事務


主題訪問量從100修改到150

查詢主題訪問量為150



提交事務

以上表為例,事務A讀取主題訪問量時讀取到了事務B沒有提交的數據150。

如果事務B失敗進行回滾,那麼修改後的值還是會回到100。

然而事務A獲取的數據是修改後的數據,這就有問題了。

2. 不可重複讀

事務讀取同一個數據,返回結果先後不一致問題。

事務A

事務B

執行事務

執行事務

查詢主題訪問量為100



修改主題訪問量為200


提交事務

查詢主題訪問量為200


上表格中,事務A在先後獲取主題訪問量時,返回的數據不一致。

也就是說在事務A執行的過程中,訪問量被其它事務修改,那麼事務A查詢到的結果就是不可靠的。

**髒讀與不可重複讀的區別**

髒讀讀取的是另一個事務沒有提交的數據,而不可重複讀讀取的是另一個事務已經提交的數據。

3. 幻讀

事務按照範圍查詢,兩次返回結果不同。

事務A

事務B

開始事務

開始事務

查詢訪問量100-200的主題個數為100



此時有一篇新的文章訪問量達到了150


提交事務

再次查詢訪問量100-200的主題個數為101


以上表為例,當對100-200訪問量的主題做統計時,第一次找到了100個,第二次找到了101個。

4. 區別

  • 髒讀讀取的是另一個事務沒有提交的數據,而不可重複讀讀取的是另一個事務已經提交的數據。

  • 幻讀和不可重複讀都是讀取了另一條已經提交的事務(這點與髒讀不同),所不同的是不可重複讀查詢的都是同一個數據項,而幻讀針對的是一批數據整體(比如數據的個數)。

針對以上的三個問題,產生了四種隔離級別。

在第二節中對隔離性進行了簡單的概念解釋,實際上的隔離性是很複雜的。

在MySQL中定義了四種隔離級別,分別為未提交讀 (Read Uncommitted)、提交讀 (Read committed)、可重複讀取 (Repeatable Read)、可串行化 (Serializable)。

  • 未提交讀 (Read Uncommitted):倆個事務同時運行,有一個事務修改了數據,但未提交,另一個事務是可以讀取到沒有提交的數據。這種情況被稱之為

    髒讀

  • 提交讀(Read committed):一個事務在未提交之前,所做的任何操作其它事務不可見。這種隔離級別也被稱之為

    不可重複讀

    。因為會存在兩次同樣的查詢,返回的數據可能會得到不一樣的結果。

  • 可重複讀(Repeatable Read):這種隔離級別

    解決了髒讀

    問題,但是還是

    存在幻讀

    問題,這種隔離界別在MySQL的innodb引擎中是默認級別。MySQL在解決幻讀問題使用間隙鎖來解決幻讀問題。

  • 可串行化 (Serializable):這種級別是最高的,強制事務進行串行執行,解決了可重複讀的幻讀問題。

隔離級別

髒讀

不可重讀讀

幻讀

未提交讀 (Read Uncommitted)

可能發生

可能發生

可能發生

提交讀(Read committed)

不可能發生

可能發生

可能發生

可重複讀(Repeatable Read)

不可能發生

不可能發生

可能發生

可串行化 (Serializable)

不可能發生

不可能發生

不可能發生

對於隔離級別,級別越高併發就越低,而級別越低會引發髒讀、不可重複讀、幻讀的問題。

因此在MySQL中使用可重複讀(Repeatable Read)作為默認級別。

作為默認級別是如何解決並處理相應問題的呢!

那麼針對這一問題,是一個難啃的骨頭,咔咔將在下一期MVCC文章專門來介紹這塊。

四、事務日誌以及事務異常如何應對

MySQL的版本號為8.0

在Innodb中事務的日誌分為兩種,回滾日誌、重做日誌。

先來看一下倆個日誌的存放位置吧!

在Linux下的MySQL事務日誌存放在/var/lib/mysql這個位置中。

面試上來就問的MySQL事務

從上圖中可以看到分別為ib_logfile、undo_倆個文件。

ib_logfile文件為重做日誌

undo_文件為回滾日誌

在這裡估計有點小夥伴會有點迷糊這個回滾日誌。

那是因為在MySQL5.6默認回滾日誌沒有進行獨立表空間存儲,而是存放到了ibdata文件中。

獨立表空間存儲從MySQL5.6後就已經支持了,但是需要自行配置。

在MySQL8.0是由innodb_undo_tablespaces 這個參數來設置回滾日誌獨立空間個數,這個參數的範圍為0-128。

默認值為0表示不開啟獨立的回滾日誌,且回滾日誌存儲在ibdata文件中。

這個參數是在初始化數據庫時指定的,實例一旦創建這個參數是不能改動的。

如果設置的innodb_undo_tablespaces 值大於實例創建時的個數,則會啟動失敗。

面試上來就問的MySQL事務

1. 重做日誌(redo log)(持久性實現原理)

事務的持久性就是通過重做日誌來實現的。

當提交事務之後,並不是直接修改數據庫的數據的,而是先保證將相關的操作記錄到redo日誌中。

數據庫會根據相應的機制將內存的中的髒頁數據刷新到磁盤中。

面試上來就問的MySQL事務

上圖是一個簡單的重做日誌寫入流程。

在上圖中提到倆個陌生概念,Buffer pool、redo log buffer,這個倆個都是Innodb存儲引擎的內存區域的一部分。

而redo log file是位於磁盤位置。

也就說當有DML(insert、update、delete)操作時,數據會先寫入Buffer pool,然後再寫到重做日誌緩衝區。

重做日誌緩衝區會根據刷盤機制來進行寫入重做日誌中。

這個機制的設置參數為innodb_flush_log_at_trx_commit ,參數分別為0,1,2

面試上來就問的MySQL事務

上圖即為重做日誌的寫入策略。

  • 當這個參數的值為0的時,提交事務之後,會把數據存放到redo log buffer中,然後每秒將數據寫進磁盤文件

  • 當這個參數的值為1的時,提交事務之後,就必須把redo log buffer從內存刷入到磁盤文件裡去,只要事務提交成功,那麼redo log就必然在磁盤裡了。

  • 當這個參數的值為2的情況,提交事務之後,把redo log buffer日誌寫入磁盤文件對應的os cache緩存裡去,而不是直接進入磁盤文件,1秒後才會把os cache裡的數據寫入到磁盤文件裡去。

2. 服務器異常停止對事務如何應對(事務寫入過程)

  • 當參數為0時,前一秒的日誌都保存在日誌緩衝區,也就是內存上,如果機器宕掉,可能丟失1秒的事務數據。

  • 當參數為1時,數據庫對IO的要求就非常高了,如果底層的硬件提供的IOPS比較差,那麼MySQL數據庫的併發很快就會由於硬件IO的問題而無法提升。

  • 當參數為2時,數據是直接寫進了os cache緩存,這部分屬於操作系統部分,如果操作系統部分損壞或者斷電的情況會丟失1秒內的事務數據,這種策略相對於第一種就安全了很多,並且對IO要求也沒有那麼高。

小結

關於性能:0>2>1

關於安全:1>2>0

根據以上結論,所以說在MySQL數據庫中,刷盤策略默認值為1,保證事務提交之後,數據絕對不會丟失。

3. 回滾日誌(undo log)(原子性實現原理)

回滾日誌保證了事務的原子性。

回滾日誌相對重做日誌來說沒有那麼複雜的流程。

當事務對數據庫進行修改時,Innodb引擎不僅會記錄redo log日誌,還會記錄undo log日誌。

如果事務失敗,或者執行了rollback,為了保證事務的原子性,就必須利用undo log日誌來進行回滾操作。

回滾日誌的存儲形式如下。

在undo log日誌文件,事務中使用的每條insert都對應了一條delete,每條update也都對應一條相反的update語句

面試上來就問的MySQL事務

注意:

系統發生宕機或者數據庫進程直接被殺死。

當用戶再次啟動數據庫進程時,還能夠立刻通過查詢回滾日誌將之前未完成的事務進程回滾。

這也就需要回滾日誌必須先把數據持久化到磁盤上,是需要先寫日誌後寫數據庫的主要原因。

回滾日誌不僅僅可以保證事務的原子性,還是實現mvcc的重要因素。

以上就是關於事務的兩大日誌,重做日誌、回滾日誌的理解。

五、鎖機制

鎖在MySQL中是是非常重要的一部分,鎖對MySQL數據訪問併發有著舉足輕重的作用。

所以說鎖的內容以及細節是十分繁瑣的,本節只是對Innodb鎖的一個大概整理。

MySQL中有三類鎖,分別為行鎖、表鎖、頁鎖。

首先需要明確的是這三類鎖是是歸屬於那種存儲引擎的。

  • 行鎖:Innodb存儲引擎

  • 表鎖:Myisam、MEMORY存儲引擎

  • 頁鎖:BDB存儲引擎

1. 行鎖

行鎖又分為共享鎖、排它鎖,也被稱之為讀鎖、寫鎖,Innodb存儲引擎的默認鎖。

共享鎖(S):

假設一個事務對數據A加了共享鎖(S),則這個事務只能讀A的數據。

其它事務只能再對數據A添加共享鎖(S),而不能添加排它鎖(X),直到這個事務釋放了數據A的共享鎖(S)。

這就保證了其它事務也可以讀取A的數據,但是在這個事務沒有釋放在A數據上的共享鎖(S)之前不能對A做任何修改。

排它鎖(X)

假設一個事務對數據A添加了排它鎖(X),則只允許這個事務讀取和修改數據A。

其它任何事務都不能再對數據A添加任何類型的鎖,直至這個事務釋放了數據A上的鎖。

排它鎖阻止其它事務獲取相同數據的共享鎖(S)、排它鎖(X),直至釋放排它鎖(X)。

特點

  • 只針對單一數據進行加鎖

  • 開銷大

  • 加鎖慢

  • 會出現死鎖

  • 鎖粒度最小,發生鎖衝突的概率越低,併發越高。

還記得在上文中提到的事務併發帶來的問題、髒讀、不可重讀讀、幻讀。

學習到了這裡,應該就明白可重複讀(Repeatable Read)如何解決髒讀、不可重讀讀了。

髒讀、和不可重複讀的解決方案很簡單,寫前加排它鎖(X),事務結束才釋放,讀前加共享鎖(S),事務結束就釋放

2. 表鎖

表鎖又分為表共享讀鎖、表獨佔寫鎖,也被稱之為讀鎖、寫鎖,Myisa存儲引擎的默認鎖。

  • 表共享讀鎖 : 針對同一個份數據,可以同時讀取互不影響,但不允許寫操作。

  • 表獨佔寫鎖 :當寫操作沒有結束時,會阻塞所有讀和寫。

特點

  • 對整張表加鎖

  • 開銷小

  • 加鎖快

  • 無死鎖

  • 鎖粒度最大,發生鎖衝突的概率越大,併發越小。

本文主要說明Innodb和Myisam的鎖,也鎖不就不做詳細說明了。

3. 如何加鎖

表鎖

  • 隱式加鎖:默認自動加鎖釋放鎖,select加讀鎖、update、insert、delete加寫鎖。

  • 手動加鎖:lock table tableName read;(添加讀鎖)、lock table tableName write(添加寫鎖)。

  • 手動解鎖:unlock table tableName(釋放單表)、unlock table(釋放所有表)

行鎖

  • 隱式加鎖:默認自動加鎖釋放鎖,只有select不會加鎖,update、insert、delete加排它鎖。

  • 手動加共享鎖:select id name from user lock in share mode;

  • 手動加排它鎖:select id name form user for update;

  • 解鎖:正常提交事務(commit)、事務回滾(rollback)、kill進程。

六、總結

本文主要對事務的重點知識點進行解讀,內容總結。

事務四大特徵實現原理

  • 原子性:使用事務日誌的回滾日誌(undo log)實現

  • 隔離性:使用mvcc實現(幻讀問題除外)

  • 持久性:使用事務日誌的重做日誌(redo log)實現

  • 一致性:是事務追求的最終目標,原子性、隔離性、持久性都是為了保證數據庫一致性而存在

事務併發出現問題的區別

  • 髒讀與不可重複讀的區別:髒讀是讀取沒有提交事務的數據、不可重複讀讀取的是已提交事務的數據。

  • 幻讀與不可重複讀的區別:都是讀取的已提交事務的數據(與髒讀不同),幻讀針對的是一批數據,例如個數。不可重複讀針對的是單一數據。

事務日誌

  • 重做日誌(redo log):實現了事務的持久性,提交事務後不是直接修改數據庫,而是保證每次事務操作都寫入redo log中。並且落盤會有三種策略(詳細看四-1節)。

  • 回滾日誌(undo log):實現了事務的原子性,針對DML的操作,都會有記錄相反的DML操作。


分享到:


相關文章: