面試官上來就問MySQL事務,瑟瑟發抖...

關於學習這件事情寧肯花點時間系統學習,也不要東一榔頭西一棒槌,都說學習最好的方式就是系統的學習,但願看完本文會讓你對事務有必定的理解。數據庫版本爲8.0mysql

系列文章

1. 揭開MySQL索引神祕面紗 2. MySQL查詢優化必備 3. 上來就問MySQL事務,瑟瑟發抖...sql

1、什麼是事務

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

也就是說若是有任何一條語句由於崩潰或者其它緣由致使執行失敗,那麼未執行的語句都不會再執行,已經執行的語句會進行回滾操做,這個過程被稱之爲事務。緩存

例:安全

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

一經審覈爲違規主題,則進行刪除主題的操做,但不只僅要刪除主題還要刪除主題下的帖子、瀏覽量,關於這個主題的一切信息都須要進行清理。併發

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

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

清理流程

2、事務四大特徵

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

1. 原子性

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

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

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

2. 一致性

一致性,字面意思就是先後一致唄!在數據庫中無論進行任何操做,都是從一個一致性轉移到另外一個一致性。

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

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

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

3. 隔離性

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

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

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

4. 持久性

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

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

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

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文章專門來介紹這塊。

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

MySQL的版本號爲8.0

在Innodb中事務的日誌分爲倆種,回滾日誌、重作日誌。

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

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

事務日誌存放位置

從上圖中能夠看到分別爲ib_logfile、undo_倆個文件。

ib_logfile文件爲重作日誌

undo_文件爲回滾日誌

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

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

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

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

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

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

若是設置的innodb_undo_tablespaces 值大於實例建立時的個數,則會啓動失敗。

回滾日誌獨立表空間個數

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

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

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

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

重作日誌寫入流程

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

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

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

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

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

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

刷盤策略

上圖即爲重作日誌的寫入策略。

  • 當這個參數的值爲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語句

回滾日誌存儲方式

注意:

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

當用戶再次啓動數據庫進程時,還可以馬上經過查詢回滾日誌將以前未完成的事務進程回滾。

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

回滾日誌不只僅能夠保證事務的原子性,仍是實現mvcc的重要因素。

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

5、鎖機制

鎖在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進程。

6、總結

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

事務四大特徵實現原理

  • 原子性:使用事務日誌的回滾日誌(undo log)實現
  • 隔離性:使用mvcc實現(幻讀問題除外)
  • 持久性:使用事務日誌的重作日誌(redo log)實現
  • 一致性:是事務追求的最終目標,原子性、隔離性、持久性都是爲了保證數據庫一致性而存在

事務併發出現問題的區別

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

事務日誌

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

堅持學習、堅持寫博、堅持分享是咔咔從業以來一直所秉持的信念。但願在偌大互聯網中咔咔的文章能帶給你一絲絲幫助。我是咔咔,下期見。

相關文章
相關標籤/搜索