mysql 事務就這一篇!
開篇
相信大家都用過事務以及瞭解他的特點,如:
-
原子性 (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)
圖片
start transaction;select balance from bank where ;// 生成 重做日誌 balance=600update bank set balance = balance - 400; // 生成 重做日誌 amount=400update finance set amount = amount + 400;commit;
redo log 有什麼作用?
mysql 爲了提升性能不會把每次的修改都實時同步到磁盤,而是會先存到 Boffer Pool(緩衝池) 裏頭,把這個當作緩存來用。然後使用後臺線程去做緩衝池和磁盤之間的同步。
那麼問題來了,如果還沒來的同步的時候宕機或斷電了怎麼辦?還沒來得及執行上面圖中紅色的操作。這樣會導致丟失部分已提交事務的修改信息!
所以引入了 redo log 來記錄已成功提交事務的修改信息,並且會把 redo log 持久化到磁盤,系統重啓之後再讀取 redo log 恢復最新數據。
總結:redo log 是用來恢復數據的用於保障,已提交事務的持久化特性
2.undo log
什麼是 undo log ?
undo log 叫做回滾日誌,用於記錄數據被修改前的信息。他正好跟前面所說的重做日誌所記錄的相反,重做日誌記錄數據被修改後的信息。undo log 主要記錄的是數據的邏輯變化,爲了在發生錯誤時回滾之前的操作,需要將之前的操作都記錄下來,然後在發生錯誤時纔可以回滾。
還用上面那兩張表
每次寫入數據或者修改數據之前都會把修改前的信息記錄到 undo log。
undo log 有什麼作用?
undo log 記錄事務修改之前版本的數據信息,因此假如由於系統錯誤或者 rollback 操作而回滾的話可以根據 undo log 的信息來進行回滾到沒被修改前的狀態。
總結:undo log 是用來回滾數據的用於保障 未提交事務的原子性
mysql 鎖技術以及 MVCC 基礎
1. mysql 鎖技術
當有多個請求來讀取表中的數據時可以不採取任何操作,但是多個請求裏有讀請求,又有修改請求時必須有一種措施來進行併發控制。不然很有可能會造成不一致。
讀寫鎖
解決上述問題很簡單,只需用兩種鎖的組合來對讀寫請求進行控制即可,這兩種鎖被稱爲:
共享鎖 (shared lock), 又叫做 "讀鎖" 讀鎖是可以共享的,或者說多個讀請求可以共享一把鎖讀數據,不會造成阻塞。
排他鎖 (exclusive lock), 又叫做 "寫鎖" 寫鎖會排斥其他所有獲取鎖的請求,一直阻塞,直到寫入完成釋放鎖。
總結:通過讀寫鎖,可以做到讀讀可以並行,但是不能做到寫讀,寫寫並行 事務的隔離性就是根據讀寫鎖來實現的!!!這個後面再說。
2. MVCC 基礎
MVCC (MultiVersion Concurrency Control) 叫做多版本併發控制。
InnoDB 的 MVCC ,是通過在每行記錄的後面保存兩個隱藏的列來實現的。這兩個列,一個保存了行的創建時間,一個保存了行的過期時間,當然存儲的並不是實際的時間值,而是系統版本號。
以上片段摘自《高性能 Mysql》這本書對 MVCC 的定義。他的主要實現思想是通過數據多版本來做到讀寫分離。從而實現不加鎖讀進而做到讀寫並行。關注 Java 技術棧公衆號在後臺回覆 mysql 獲取系列教程。
MVCC 在 mysql 中的實現依賴的是 undo log 與 read view
-
undo log :undo log 中記錄某行數據的多個版本的數據。
-
read view : 用來判斷當前版本數據的可見性
事務的實現
前面講的重做日誌,回滾日誌以及鎖技術就是實現事務的基礎。
-
事務的原子性是通過 undo log 來實現的
-
事務的持久性性是通過 redo log 來實現的
-
事務的隔離性是通過 (讀寫鎖 + MVCC) 來實現的
-
而事務的終極大 boss 一致性是通過原子性,持久性,隔離性來實現的!!!
原子性,持久性,隔離性折騰半天的目的也是爲了保障數據的一致性!
總之,ACID 只是個概念,事務最終目的是要保障數據的可靠性,一致性。
1. 原子性的實現
什麼是原子性:
一個事務必須被視爲不可分割的最小工作單位,一個事務中的所有操作要麼全部成功提交,要麼全部失敗回滾,對於一個事務來說不可能只執行其中的部分操作,這就是事務的原子性。
上面這段話取自《高性能 MySQL》這本書對原子性的定義,原子性可以概括爲就是要實現要麼全部失敗,要麼全部成功。
以上概念相信大家夥兒都瞭解,那麼數據庫是怎麼實現的呢?就是通過回滾操作。所謂回滾操作就是當發生錯誤異常或者顯式的執行 rollback 語句時需要把數據還原到原先的模樣,所以這時候就需要用到 undo log 來進行回滾,接下來看一下 undo log 在實現事務原子性時怎麼發揮作用的
1.1 undo log 的生成
假設有兩個表 bank 和 finance,表中原始數據如圖所示,當進行插入,刪除以及更新操作時生成的 undo log 如下面圖所示:
從上圖可以瞭解到數據的變更都伴隨着回滾日誌的產生:(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 進行回滾
回滾操作就是要還原到原來的狀態,undo log 記錄了數據被修改前的信息以及新增和被刪除的數據信息,根據 undo log 生成回滾語句,比如:
(1) 如果在回滾日誌裏有新增數據記錄,則生成刪除該條的語句
(2) 如果在回滾日誌裏有刪除數據記錄,則生成生成該條的語句
(3) 如果在回滾日誌裏有修改數據記錄,則生成修改到原先數據的語句
2. 持久性的實現
事務一旦提交,其所做的修改會永久保存到數據庫中,此時即使系統崩潰修改的數據也不會丟失。
先了解一下 MySQL 的數據存儲機制,MySQL 的表數據是存放在磁盤上的,因此想要存取的時候都要經歷磁盤 IO, 然而即使是使用 SSD 磁盤 IO 也是非常消耗性能的。爲此,爲了提升性能 InnoDB 提供了緩衝池 (Buffer Pool),Buffer Pool 中包含了磁盤數據頁的映射,可以當做緩存來使用:
讀數據:會首先從緩衝池中讀取,如果緩衝池中沒有,則從磁盤讀取在放入緩衝池;
寫數據:會首先寫入緩衝池,緩衝池中的數據會定期同步到磁盤中;
上面這種緩衝池的措施雖然在性能方面帶來了質的飛躍,但是它也帶來了新的問題,當 MySQL 系統宕機,斷電的時候可能會丟數據!!!
因爲我們的數據已經提交了,但此時是在緩衝池裏頭,還沒來得及在磁盤持久化,所以我們急需一種機制需要存一下已提交事務的數據,爲恢復數據使用。
於是 redo log 就派上用場了。下面看下 redo log 是什麼時候產生的
既然 redo log 也需要存儲,也涉及磁盤 IO 爲啥還用它?
(1)redo log 的存儲是順序存儲,而緩存同步是隨機操作。
(2)緩存同步是以數據頁爲單位的,每次傳輸的數據大小大於 redo log。
3. 隔離性實現
隔離性是事務 ACID 特性裏最複雜的一個。在 SQL 標準裏定義了四種隔離級別,每一種級別都規定一個事務中的修改,哪些是事務之間可見的,哪些是不可見的。
級別越低的隔離級別可以執行越高的併發,但同時實現複雜度以及開銷也越大。
Mysql 隔離級別有以下四種(級別由低到高):
-
READ UNCOMMITED (未提交讀)
-
READ COMMITED (提交讀)
-
REPEATABLE READ (可重複讀)
-
SERIALIZABLE (可重複讀)
只要徹底理解了隔離級別以及他的實現原理就相當於理解了 ACID 裏的隔離型。前面說過原子性,隔離性,持久性的目的都是爲了要做到一致性,但隔離型跟其他兩個有所區別,原子性和持久性是爲了要實現數據的可性保障靠,比如要做到宕機後的恢復,以及錯誤後的回滾。
那麼隔離性是要做到什麼呢?隔離性是要管理多個併發讀寫請求的訪問順序。 這種順序包括串行或者是並行說明一點,寫請求不僅僅是指 insert 操作,又包括 update 操作。
總之,從隔離性的實現可以看出這是一場數據的可靠性與性能之間的權衡。
-
可靠性性高的,併發性能低 (比如 Serializable)
-
可靠性低的,併發性能高 (比如 Read Uncommited)
READ UNCOMMITTED
在 READ UNCOMMITTED 隔離級別下,事務中的修改即使還沒提交,對其他事務是可見的。事務可以讀取未提交的數據,造成髒讀。
因爲讀不會加任何鎖,所以寫操作在讀的過程中修改數據,所以會造成髒讀。好處是可以提升併發處理性能,能做到讀寫並行。
換句話說,讀的操作不能排斥寫請求。
優點:讀寫並行,性能高 缺點:造成髒讀
READ COMMITTED
一個事務的修改在他提交之前的所有修改,對其他事務都是不可見的。其他事務能讀到已提交的修改變化。在很多場景下這種邏輯是可以接受的。
InnoDB 在 READ COMMITTED,使用排它鎖, 讀取數據不加鎖而是使用了 MVCC 機制。或者換句話說他採用了讀寫分離機制。但是該級別會產生不可重讀以及幻讀問題。
什麼是不可重讀?
在一個事務內多次讀取的結果不一樣。
爲什麼會產生不可重複讀?
這跟 READ COMMITTED 級別下的 MVCC 機制有關係,在該隔離級別下每次 select 的時候新生成一個版本號,所以每次 select 的時候讀的不是一個副本而是不同的副本。分享一份完整的 MySQL 開發規範,推薦看下。
在每次 select 之間有其他事務更新了我們讀取的數據並提交了,那就出現了不可重複讀
REPEATABLE READ(Mysql 默認隔離級別)
在一個事務內的多次讀取的結果是一樣的。這種級別下可以避免,髒讀,不可重複讀等查詢問題。mysql 有兩種機制可以達到這種隔離級別的效果,分別是採用讀寫鎖以及 MVCC。
採用讀寫鎖實現:
優點:實現起來簡單
缺點:無法做到讀寫並行
採用 MVCC 實現:
爲什麼能可重複讀?因爲多次讀取只生成一個版本,讀到的自然是相同數據。
優點:讀寫並行
缺點:實現的複雜度高
但是在該隔離級別下仍會存在幻讀的問題,關於幻讀的解決我打算另開一篇來介紹。
SERIALIZABLE
該隔離級別理解起來最簡單,實現也最簡單。在隔離級別下除了不會造成數據不一致問題,沒其他優點。
4. 一致性的實現
數據庫總是從一個一致性的狀態轉移到另一個一致性的狀態.
下面舉個例子: zhangsan 從銀行卡轉 400 到理財賬戶
start transaction;select balance from bank where ;// 生成 重做日誌 balance=600update bank set balance = balance - 400; // 生成 重做日誌 amount=400update finance set amount = amount + 400;commit;
-
假如執行完
update bank set balance = balance - 400;
之發生異常了,銀行卡的錢也不能平白無辜的減少,而是回滾到最初狀態。 -
又或者事務提交之後,緩衝池還沒同步到磁盤的時候宕機了,這也是不能接受的,應該在重啓的時候恢復並持久化。
-
假如有併發事務請求的時候也應該做好事務之間的可見性問題,避免造成髒讀,不可重複讀,幻讀等。在涉及併發的情況下往往在性能和一致性之間做平衡,做一定的取捨,所以隔離性也是對一致性的一種破壞。
總結
本文出發點是想講一下 Mysql 的事務的實現原理。
實現事務採取了哪些技術以及思想?
-
原子性:使用 undo log ,從而達到回滾
-
持久性:使用 redo log,從而達到故障後恢復
-
隔離性:使用鎖以及 MVCC, 運用的優化思想有讀寫分離,讀讀並行,讀寫並行
-
一致性:通過回滾,以及恢復,和在併發環境下的隔離做到一致性。
鏈接:https://www.jianshu.com/p/93dc93d2aebc
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/BSEl8ak3NC8uctGOgLD_8w