【Mysql核心技術】聊聊事務的實現原理

捂耳聽風發表於2019-04-18

開篇

相信大家都用過事務以及瞭解他的特點,如原子性(Atomicity),一致性(Consistency),隔離型(Isolation)以及永續性(Durability)等。今天想跟大家一起研究下事務內部到底是怎麼實現的,在講解前我想先丟擲個問題:
事務想要做到什麼效果?

按我理解,無非是要做到可靠性以及併發處理

可靠性:資料庫要保證當insert或update操作時拋異常或者資料庫crash的時候需要保障資料的操作前後的一致,想要做到這個,我需要知道我修改之前和修改之後的狀態,所以就有了undo log和redo log。

併發處理:也就是說當多個併發請求過來,並且其中有一個請求是對資料修改操作的時候會有影響,為了避免讀到髒資料,所以需要對事務之間的讀寫進行隔離,至於隔離到啥程度得看業務系統的場景了,實現這個就得用MySQL 的隔離級別。

下面我首先講實現事務功能的三個技術,分別是日誌檔案(redo log 和 undo log),鎖技術以及MVCC,然後再講事務的實現原理,包括原子性是怎麼實現的,隔離型是怎麼實現的等等。最後在做一個總結,希望大家能夠耐心看完

  • redo log與undo log介紹
  • mysql鎖技術以及MVCC基礎
  • 事務的實現原理
  • 總結

二、 redo log 與 undo log介紹

1. redo log

什麼是redo log ?

redo log叫做重做日誌,是用來實現事務的永續性。該日誌檔案由兩部分組成:重做日誌緩衝(redo log buffer)以及重做日誌檔案(redo log),前者是在記憶體中,後者在磁碟中。當事務提交之後會把所有修改資訊都會存到該日誌中。假設有個表叫做tb1(id,username) 現在要插入資料(3,ceshi)

【Mysql核心技術】聊聊事務的實現原理

start transaction;
select balance from bank where name="zhangsan";
// 生成 重做日誌 balance=600
update bank set balance = balance - 400; 
// 生成 重做日誌 amount=400
update finance set amount = amount + 400;
commit;
複製程式碼

【Mysql核心技術】聊聊事務的實現原理

redo log 有什麼作用?

mysql 為了提升效能不會把每次的修改都實時同步到磁碟,而是會先存到Boffer Pool(緩衝池)裡頭,把這個當作快取來用。然後使用後臺執行緒去做緩衝池和磁碟之間的同步

那麼問題來了,如果還沒來的同步的時候當機或斷電了怎麼辦?還沒來得及執行上面圖中紅色的操作。這樣會導致丟部分已提交事務的修改資訊!

所以引入了redo log來記錄已成功提交事務的修改資訊,並且會把redo log持久化到磁碟,系統重啟之後在讀取redo log恢復最新資料。

總結:
redo log是用來恢復資料的 用於保障,已提交事務的持久化特性

2.undo log

什麼是 undo log ?

undo log 叫做回滾日誌,用於記錄資料被修改前的資訊。他正好跟前面所說的重做日誌所記錄的相反,重做日誌記錄資料被修改後的資訊。undo log主要記錄的是資料的邏輯變化,為了在發生錯誤時回滾之前的操作,需要將之前的操作都記錄下來,然後在發生錯誤時才可以回滾。

還用上面那兩張表

【Mysql核心技術】聊聊事務的實現原理

每次寫入資料或者修改資料之前都會把修改前的資訊記錄到 undo log。

undo log 有什麼作用?

undo log 記錄事務修改之前版本的資料資訊,因此假如由於系統錯誤或者rollback操作而回滾的話可以根據undo log的資訊來進行回滾到沒被修改前的狀態。

總結:
undo log是用來回滾資料的用於保障 未提交事務的原子性


三、mysql鎖技術以及MVCC基礎

1. mysql鎖技術

當有多個請求來讀取表中的資料時可以不採取任何操作,但是多個請求裡有讀請求,又有修改請求時必須有一種措施來進行併發控制。不然很有可能會造成不一致。
讀寫鎖
解決上述問題很簡單,只需用兩種鎖的組合來對讀寫請求進行控制即可,這兩種鎖被稱為:

共享鎖(shared lock),又叫做"讀鎖"
讀鎖是可以共享的,或者說多個讀請求可以共享一把鎖讀資料,不會造成阻塞。

排他鎖(exclusive lock),又叫做"寫鎖"
寫鎖會排斥其他所有獲取鎖的請求,一直阻塞,直到寫入完成釋放鎖。

【Mysql核心技術】聊聊事務的實現原理

總結:
通過讀寫鎖,可以做到讀讀可以並行,但是不能做到寫讀,寫寫並行
事務的隔離性就是根據讀寫鎖來實現的!!!這個後面再說。

2. MVCC基礎

MVCC (MultiVersion Concurrency Control) 叫做多版本併發控制。

InnoDB的 MVCC ,是通過在每行記錄的後面儲存兩個隱藏的列來實現的。這兩個列, 一個儲存了行的建立時間,一個儲存了行的過期時間, 當然儲存的並不是實際的時間值,而是系統版本號。

以上片段摘自《高效能Mysql》這本書對MVCC的定義。他的主要實現思想是通過資料多版本來做到讀寫分離。從而實現不加鎖讀進而做到讀寫並行。

MVCC在mysql中的實現依賴的是undo log與read view

  • undo log :undo log 中記錄某行資料的多個版本的資料。
  • read view :用來判斷當前版本資料的可見性

【Mysql核心技術】聊聊事務的實現原理


四、事務的實現

前面講的重做日誌,回滾日誌以及鎖技術就是實現事務的基礎。

  • 事務的原子性是通過 undo log 來實現的
  • 事務的永續性性是通過 redo log 來實現的
  • 事務的隔離性是通過 (讀寫鎖+MVCC)來實現的
  • 而事務的終極大 boss 一致性是通過原子性,永續性,隔離性來實現的!!!

原子性,永續性,隔離性折騰半天的目的也是為了保障資料的一致性!

總之,ACID只是個概念,事務最終目的是要保障資料的可靠性,一致性。

1.原子性的實現

什麼是原子性:

一個事務必須被視為不可分割的最小工作單位,一個事務中的所有操作要麼全部成功提交,要麼全部失敗回滾,對於一個事務來說不可能只執行其中的部分操作,這就是事務的原子性。

上面這段話取自《高效能MySQL》這本書對原子性的定義,原子性可以概括為就是要實現要麼全部失敗,要麼全部成功

以上概念相信大家夥兒都瞭解,那麼資料庫是怎麼實現的呢? 就是通過回滾操作。 所謂回滾操作就是當發生錯誤異常或者顯式的執行rollback語句時需要把資料還原到原先的模樣,所以這時候就需要用到undo log來進行回滾,接下來看一下undo log在實現事務原子性時怎麼發揮作用的

1.1 undo log 的生成

假設有兩個表 bank和finance,表中原始資料如圖所示,當進行插入,刪除以及更新操作時生成的undo log如下面圖所示:

【Mysql核心技術】聊聊事務的實現原理

【Mysql核心技術】聊聊事務的實現原理

從上圖可以瞭解到資料的變更都伴隨著回滾日誌的產生:
(1) 產生了被修改前資料(zhangsan,1000) 的回滾日誌

(2) 產生了被修改前資料(zhangsan,0) 的回滾日誌

根據上面流程可以得出如下結論:
1.每條資料變更(insert/update/delete)操作都伴隨一條undo log的生成,並且回滾日誌必須先於資料持久化到磁碟上
2.所謂的回滾就是根據回滾日誌做逆向操作,比如delete的逆向操作為insert,insert的逆向操作為delete,update的逆向為update等。

思考:為什麼先寫日誌後寫資料庫? ---稍後做解釋

1.2 根據undo log 進行回滾

為了做到同時成功或者失敗,當系統發生錯誤或者執行rollback操作時需要根據undo log 進行回滾

【Mysql核心技術】聊聊事務的實現原理

回滾操作就是要還原到原來的狀態,undo log記錄了資料被修改前的資訊以及新增和被刪除的資料資訊,根據undo log生成回滾語句,比如:

(1) 如果在回滾日誌裡有新增資料記錄,則生成刪除該條的語句

(2) 如果在回滾日誌裡有刪除資料記錄,則生成生成該條的語句

(3) 如果在回滾日誌裡有修改資料記錄,則生成修改到原先資料的語句

2.永續性的實現

事務一旦提交,其所作做的修改會永久儲存到資料庫中,此時即使系統崩潰修改的資料也不會丟失。

先了解一下MySQL的資料儲存機制,MySQL的表資料是存放在磁碟上的,因此想要存取的時候都要經歷磁碟IO,然而即使是使用SSD磁碟IO也是非常消耗效能的。 為此,為了提升效能InnoDB提供了緩衝池(Buffer Pool),Buffer Pool中包含了磁碟資料頁的對映,可以當做快取來使用:
讀資料:會首先從緩衝池中讀取,如果緩衝池中沒有,則從磁碟讀取在放入緩衝池;
寫資料:會首先寫入緩衝池,緩衝池中的資料會定期同步到磁碟中;

上面這種緩衝池的措施雖然在效能方面帶來了質的飛躍,但是它也帶來了新的問題,當MySQL系統當機,斷電的時候可能會丟資料!!!

因為我們的資料已經提交了,但此時是在緩衝池裡頭,還沒來得及在磁碟持久化,所以我們急需一種機制需要存一下已提交事務的資料,為恢復資料使用。

於是 redo log就派上用場了。下面看下redo log是什麼時候產生的

【Mysql核心技術】聊聊事務的實現原理

既然redo log也需要儲存,也涉及磁碟IO為啥還用它?

(1)redo log 的儲存是順序儲存,而快取同步是隨機操作。

(2)快取同步是以資料頁為單位的,每次傳輸的資料大小大於redo log。


3.隔離性實現

隔離性是事務ACID特性裡最複雜的一個。在SQL標準裡定義了四種隔離級別,每一種級別都規定一個事務中的修改,哪些是事務之間可見的,哪些是不可見的。

級別越低的隔離級別可以執行越高的併發,但同時實現複雜度以及開銷也越大。

Mysql 隔離級別有以下四種(級別由低到高):

  • READ UNCOMMITED (未提交讀)
  • READ COMMITED (提交讀)
  • REPEATABLE READ (可重複讀)
  • SERIALIZABLE (可重複讀)

只要徹底理解了隔離級別以及他的實現原理就相當於理解了ACID裡的隔離型。前面說過原子性,隔離性,永續性的目的都是為了要做到一致性,但隔離型跟其他兩個有所區別,原子性和永續性是為了要實現資料的可性保障靠,比如要做到當機後的恢復,以及錯誤後的回滾。

那麼隔離性是要做到什麼呢? 隔離性是要管理多個併發讀寫請求的訪問順序。 這種順序包括序列或者是並行
說明一點,寫請求不僅僅是指insert操作,又包括update操作。

【Mysql核心技術】聊聊事務的實現原理

總之,從隔離性的實現可以看出這是一場資料的可靠性與效能之間的權衡。

  • 可靠性性高的,併發效能低(比如 Serializable)
  • 可靠性低的,併發效能高(比如 Read Uncommited)

READ UNCOMMITTED

在READ UNCOMMITTED隔離級別下,事務中的修改即使還沒提交,對其他事務是可見的。事務可以讀取未提交的資料,造成髒讀。

因為讀不會加任何鎖,所以寫操作在讀的過程中修改資料,所以會造成髒讀。好處是可以提升併發處理效能,能做到讀寫並行

換句話說,讀的操作不能排斥寫請求。

【Mysql核心技術】聊聊事務的實現原理

優點:讀寫並行,效能高
缺點:造成髒讀

READ COMMITTED

一個事務的修改在他提交之前的所有修改,對其他事務都是不可見的。其他事務能讀到已提交的修改變化。在很多場景下這種邏輯是可以接受的。

InnoDB在 READ COMMITTED,使用排它鎖,讀取資料不加鎖而是使用了MVCC機制。或者換句話說他採用了讀寫分離機制
但是該級別會產生不可重讀以及幻讀問題。

什麼是不可重讀?

在一個事務內多次讀取的結果不一樣。

為什麼會產生不可重複讀?

這跟 READ COMMITTED 級別下的MVCC機制有關係,在該隔離級別下每次 select的時候新生成一個版本號,所以每次select的時候讀的不是一個副本而是不同的副本。

在每次select之間有其他事務更新了我們讀取的資料並提交了,那就出現了不可重複讀

【Mysql核心技術】聊聊事務的實現原理

REPEATABLE READ(Mysql預設隔離級別)

在一個事務內的多次讀取的結果是一樣的。這種級別下可以避免,髒讀,不可重複讀等查詢問題。mysql 有兩種機制可以達到這種隔離級別的效果,分別是採用讀寫鎖以及MVCC。

採用讀寫鎖實現

【Mysql核心技術】聊聊事務的實現原理

為什麼能可重複度?只要沒釋放讀鎖,在次讀的時候還是可以讀到第一次讀的資料。

優點:實現起來簡單

缺點:無法做到讀寫並行

採用MVCC實現

【Mysql核心技術】聊聊事務的實現原理

為什麼能可重複度?因為多次讀取只生成一個版本,讀到的自然是相同資料。

優點:讀寫並行

缺點:實現的複雜度高

但是在該隔離級別下仍會存在幻讀的問題,關於幻讀的解決我打算另開一篇來介紹。

SERIALIZABLE

該隔離級別理解起來最簡單,實現也最單。在隔離級別下除了不會造成資料不一致問題,沒其他優點。

【Mysql核心技術】聊聊事務的實現原理

【Mysql核心技術】聊聊事務的實現原理

--摘自《高效能Mysql》

4.一致性的實現

資料庫總是從一個一致性的狀態轉移到另一個一致性的狀態.

下面舉個例子:zhangsan 從銀行卡轉400到理財賬戶

start transaction;
select balance from bank where name="zhangsan";
// 生成 重做日誌 balance=600
update bank set balance = balance - 400; 
// 生成 重做日誌 amount=400
update finance set amount = amount + 400;
commit;
複製程式碼

1.假如執行完 update bank set balance = balance - 400;之發生異常了,銀行卡的錢也不能平白無辜的減少,而是回滾到最初狀態。

2.又或者事務提交之後,緩衝池還沒同步到磁碟的時候當機了,這也是不能接受的,應該在重啟的時候恢復並持久化。

3.假如有併發事務請求的時候也應該做好事務之間的可見性問題,避免造成髒讀,不可重複讀,幻讀等。在涉及併發的情況下往往在效能和一致性之間做平衡,做一定的取捨,所以隔離性也是對一致性的一種破壞。


總結

本文出發點是想講一下Mysql的事務的實現原理。

實現事務採取了哪些技術以及思想?

  • 原子性:使用 undo log ,從而達到回滾
  • 永續性:使用 redo log,從而達到故障後恢復
  • 隔離性:使用鎖以及MVCC,運用的優化思想有讀寫分離,讀讀並行,讀寫並行
  • 一致性:通過回滾,以及恢復,和在併發環境下的隔離做到一致性。

如果覺得不錯點個贊 ?

其他文章

【Spring進階指南】Spring 為啥預設把bean設計成單例的?

【Redis面試題】Redis的字串是怎麼實現的?

Spring 原始碼分析之 bean 例項化原理?

Spring 原始碼分析之 bean 依賴注入原理(注入屬性)

Spring原始碼分析之 lazy-init 實現原理

相關文章