MySQL系列文章:算法
『MySQL』搞懂 InnoDB 鎖機制 以及 高併發下如何解決超賣問題數據庫
『MySQL』MySQL執行流程bash
一張思惟導圖看完系列文章:session
距離上一篇MySQL的文章已通過去一個月了,終於有時間來寫寫關於MySQL的事務了。本文內容默認是針對 MySQL InnoDB 引擎。數據結構
先來一張思惟導圖讀全文內容: 併發
瞭解事務以前,先來看看數據庫爲何須要有事務,假設沒有事務會有什麼影響?分佈式
舉一個轉帳的例子,假設你朋友向你借10000元,你打開APP,樂呵呵的把錢轉了,你的卡里已經少了10000元,可是你打電話給朋友時,你朋友說沒有收到啊,你這時候確定賣銀行怎麼不靠譜,沒到帳怎麼把我卡里的錢給扣了。ide
咱們來捋一捋上述銀行發生的過程,簡單的分三步:
A發起轉帳10000給B -> A銀行卡減10000元 -> B銀行卡增長10000元。
上述案例是第三步出現了問題,若是有事務,則不會發生案例中的事情,能夠理解爲事務就是這三個步驟是一根繩子上的螞蚱,要麼都成功,要麼都失敗。
因此數據庫引入事務的主要目的是事務會把數據庫會從一種一致狀態轉換到另外一種一致狀態,數據庫提交工做時能夠確保要麼全部修改都保存,要麼全部修改都不保存。
瞭解事務,還須要瞭解事務的理論依據ACID,也能夠說事務的幾個特性。
仍是上面轉帳的例子,原子性強調轉帳從A-B的三個步驟必需要麼都成功,要麼都不成功。
原子性是整個數據庫事務是不可分割的工做單位,只有事務中的全部的數據庫操做都執行成功,纔算整個事務成功。事務中任何一個SQL執行失敗,已經執行成功的SQL語句也必須撤銷,回到執行事務的以前的狀態。
一致性是指事務將數據庫從一種一致性狀態變爲下一種一致性狀態。在事務開始以前和以後,數據庫的完整性約束沒有被破壞。
上面轉帳的例子,不管轉帳成功或者失敗,A和B加起來變化就是10000元,不會多也不會少。
隔離性要求每一個讀寫事務對其餘事務的操做對象能相互分離。
好比A轉帳的銀行是工商銀行,那麼別人在工商銀行轉帳不能干擾A的轉帳行爲。
持久性指事務一旦提交,其結果就是永久性的。
事務的實現就是如何實現ACID特性,下面一圖下概況下:
由上圖看,事務的實現經過 redo_log 和 undo_log, 以及鎖實現,鎖實現事務的隔離,上一篇已經講解InnoDB的鎖,須要瞭解的朋友能夠查看上一篇文章。
redo_log 實現持久化和原子性,而undo_log實現一致性,二種日誌都可以視爲一種恢復操做,redo_log是恢復提交事務修改的頁操做,而undo_log是回滾行記錄到特定版本。兩者記錄的內容也不一樣,redo_log是物理日誌,記錄頁的物理修改操做,而undo_log是邏輯日誌,根據每行記錄進行記錄。
redo_log 重作日誌上面已經提到實現持久化和原子性,重作日誌由兩部分組成,一是內存中的重作日誌緩存(redo log buffer),這部分是容易丟失的。二是重作日誌文件(redo log file),這部分是持久的。
知道redo_log是什麼?還須要瞭解其更新流程以及redo log存的是什麼內容和恢復機制。
先來了解第一個問題,redo log的更新流程以下圖,以一次Update 操做爲例。
爲了確保每第二天志都寫入重作日誌文件,InnoDB存儲引擎會調用一次fsync操做。
瞭解redo log存儲格式和內容以前,先來對比一下跟binlog二進制日誌由什麼不一樣,binlog主要是主從複製和進行POINT-IN-TIME的恢復,想必你們對它不陌生。
binlog只有在事務提交的時候纔會寫入,且是數據庫的上層產生的。redo log是Innodb引擎層產生的。
對比一兩者的寫入方式:
binlog是每次事務才寫入,因此每一個事務只會有一條日誌,記錄的邏輯日誌,也能夠說記錄的就是SQL語句。
redo log是事務開始就開始寫入,*T1表示事務提交。記錄的是物理格式日誌,即每一個頁的修改。
redo log默認是以block(塊)的方式爲單位進行存儲,每一個塊是512個字節。不一樣的數據庫引擎有對應的重作日誌格式,Innodb的存儲管理是基於頁的,因此其重作日誌也是基於頁的。
redo log格式:
執行一條插入語句,重作日誌大體爲:
INSERT INTO user SELECT 1,2;
|
page(2,3), offset 32, value 1,2 # 主鍵索引
page(2,4), offset 64, value 2 # 輔助索引
複製代碼
能夠看到重作日誌存儲的格式有點看不太懂,看不懂沒有關係,主要是告訴你們,重作日誌存儲物理格式日誌,也就是基於存儲頁的修改。
再來了解一下 redo log的恢復機制:
上圖概況了重作日誌的恢復機制,先來解釋一下圖中出現的 LSN 是什麼?
LSN(Log Sequence Number) 日誌序列號,Innodb裏,LSN佔8個字節,且是單調遞增的,表明的含義有: 重作日誌寫入的總量、checkpoint的位置、頁的版本。
假設在LSN=10000的時候數據庫出現故障,磁盤中checkpoint爲10000,表示磁盤已經刷新到10000這個序列號,當前redolog的checkpoint是13000,則須要恢復10000-13000的數據。
再來想一想,redo log爲何能夠實現事務的原子性和持久性。
redo log一旦提交意味着持久化了,可是有時候須要對其進行rollback操做,那就須要undo log。
undo log是邏輯日誌,只是將數據庫邏輯的恢復到原來的樣子。並不能將數據庫物理地恢復到執行語句或者事務以前的樣子。雖然全部的邏輯修改均被取消了,可是數據結構和頁自己在回滾先後可能不同了。
既然是邏輯日誌,能夠理解爲它存儲的是SQL, 在事務中使用的每一條 INSERT 都對應了一條 DELETE,每一條 UPDATE 也都對應一條相反的 UPDATE 語句。
undo log 存放在數據庫內部的一個特殊段(segment)中,也叫undo段,存在於共享表空間中。
undo log實現了事務的一致性,能夠經過undo log恢復到事務以前的邏輯狀態,保證一致性。
undo log 還能夠實現MVCC(Multi-Version Concurrency Control ,多版本併發控制),多版本併發控制其實能夠經過 undo log 造成一個事務執行過程當中的版本鏈,每個寫操做會產生一個版本,數據庫發生讀的併發訪問時,讀操做訪問版本鏈,返回最合適的結果直接返回。從而讀寫操做之間沒有衝突,提升了性能。
上圖列出了事務的一些控制語句,start transaction/begin、commit、rollback相信你們都有用過。
savepoint identifier 能夠建立事務的一個保存點,執行回滾操做時能夠回滾到指定保存點,不須要回滾整個事務。
打個比例,假設你去旅遊到目標地須要三個行程,第一程 深圳到廣州高鐵,第二程 從廣州飛到雅加達,第三程 雅加達飛到某島。 若是再第三程 飛機取消行程,事務要回滾,若是要你再會深圳,你確定會心理一萬個草泥馬。由於再進入事務,第一步和第二步是不變的,因此不須要回滾,直接回滾第三步便可。
set transaction 修改事務隔離級別,好比修改會話級別的事務:
set session transaction isolation level read committed;
事務的隔離性是經過鎖來實現,上一篇也提到事務的隔離級別,這篇簡單回顧一下。
四種隔離級別,按READ-UNCOMMITTED、READ-COMMITTED、REPEATABLE-READ、SERIALIZABLE順序,隔離級別是從低到高,InnoDB默認是REPEATABLE-READ級別,此級別在其他數據庫中是會引發幻讀問題,InnoDB採用Next-Key Lock鎖算法避免了此問題,什麼是幻讀問題,請參考上一篇文章。
隔離級別越低,則事務請求的鎖和保持鎖的時間就越短。
READ-UNCOMMITTED 中文叫未提交讀,即一個事務讀到了另外一個未提交事務修改過的數據,整個過程以下圖:
如上圖,SessionA和SessionB分別開啓一個事務,SessionB中的事務先將id爲1的記錄的name列更新爲'lisi',而後Session 中的事務再去查詢這條id爲1的記錄,那麼在未提交讀的隔離級別下,查詢結果由'zhangsan'變成了'lisi',也就是說某個事務讀到了另外一個未提交事務修改過的記錄。可是若是SessionB中的事務稍後進行了回滾,那麼SessionA中的事務至關於讀到了一個不存在的數據,這種現象也稱爲髒讀。
可見READ-UNCOMMITTED是很是不安全。
READ COMMITTED 中文叫已提交讀,或者叫不可重複讀。即一個事務能讀到另外一個已經提交事務修改後的數據,若是其餘事務均對該數據進行修改並提交,該事務也能查詢到最新值。以下圖:
在第4步 SessionB 修改後,若是未提交,SessionA是讀不到,但SessionB一旦提交後,SessionA便可讀到SessionB修改的內容。
從某種程度上已提交讀是違反事務的隔離性的。
REPEATABLE READ 中文叫可重複讀,即事務能讀到另外一個已經提交的事務修改過的數據,可是第一次讀過某條記錄後,即便後面其餘事務修改了該記錄的值而且提交,該事務以後再讀該條記錄時,讀到的還是第一次讀到的值,而不是每次都讀到不一樣的數據。以下圖:
InnoDB默認是這種隔離級別,SessionB不管怎麼修改id=1的值,SessionA讀到依然是本身開啓事務第一次讀到的內容。
SERIALIZABLE 叫串行化, 上面三種隔離級別能夠進行 讀-讀 或者 讀-寫、寫-讀三種併發操做,而SERIALIZABLE不容許讀-寫,寫-讀的併發操做。 以下圖:
SessionB 對 id=1 進行修改的時候,SessionA 讀取id=1則須要等待 SessionB 提交事務。能夠理解SessionB在更新的時候加了X鎖。
分佈式事務指容許多個獨立的事務資源參與到一個全局的事務中。全局事務要求在其中的全部參與的事務要麼都提交,要麼都回滾。
InnoDB 是支持分佈式事務,由一個或多個資源管理器(Resource Managers),一個事務管理器(Transaction Manager),以及一個應用程序(Application Program)組成。
以下圖:
應用程序向一個或多個數據庫執行事務操做,事務管理器進行管理事務,經過二段式提交,第一階段全部參與的全局事務的節點都開始準備,告訴事務管理器都準備好了,能夠提交了。第二階段,事務管理器告訴每個資源管理器是執行Commit 仍是 Rollback。若是任何一個節點顯示不能提交,則全部的節點被告知須要回滾。
InnoDB的分佈式是數據庫實現的,看看數據庫外如何分佈式事務,比較常見的是TCC分佈式事務。
上圖描述了TCC分佈式事務的流程,假設電商業務中,支付後須要修改庫存,積分,物流倉儲的數據,若是一個失敗則所有回滾。
TCC分佈式事務,有三個階段,Try,Confirm, Cancel。也就是說每一個參與事務的服務都須要實現這三個接口,庫存、積分、倉儲都須要實現這三個接口。
第一階段,Try,業務應用調取各個服務的Try接口,告訴他們給我預留一個商品,有人要購買,能夠理解爲凍結,每一步都不執行成功,只是標記更新狀態。
第二階段,Confirm,確認階段,即事務協調器調取每一個服務Confirm執行事務操做,若是某一個服務的Confirm失敗,則有第三個階段。若是成功則結束事務。
第三個階段,Cancel,若是在第二個階段有一個事務提交失敗,則事務協調器調取全部業務的Cancel接口,回滾事務,將第一階段凍結的商品恢復。
在MQ中間件鏈接的上游服務和下游服務中如何實現分佈式事務了?
歡迎你們留言討論。
更多MySQL相關文章和討論,請關注公衆號:『 天澄技術雜談 』