萬事皆由不是空穴來風(fēng)悉稠,任何技術(shù)的底層都有一套嚴(yán)謹(jǐn)?shù)募軜?gòu)。
前景
說到事務(wù),我們都知道“回滾”這個概念赁炎,在一個方法上加上@Transactional 一旦程序遇到異常就會自動回滾是尖,數(shù)據(jù)庫的數(shù)據(jù)原封不動意系,到底是怎么實現(xiàn)回滾機(jī)制?其中 isolation隔離級別又是干什么饺汹?讓我們一起從Mysql底層架構(gòu)開始談起蛔添。
一、Mysql架構(gòu)分析
首先看下邏輯架構(gòu)圖,
初學(xué)Mysql這個架構(gòu)圖放在眼前有點懵比了吧迎瞧,每個模塊我們分析來看:
1夸溶、Connectors 連接器,指的是不同語言中與SQL的交互
2凶硅、Management Serveices & Utilities:系統(tǒng)管理和控制工具
3缝裁、Connection Pool: 連接池
1)管理緩沖用戶連接,線程處理等需要緩存的需求足绅。
2)負(fù)責(zé)監(jiān)聽對 MySQL Server 的各種請求捷绑,接收連接請求,轉(zhuǎn)發(fā)所有連接請求到線程管理模塊氢妈。每一個連接上 MySQL Server 的客戶端請求都會被分配(或創(chuàng)建)一個連接線程為其單獨服務(wù)粹污。
3)而連接線程的主要工作就是負(fù)責(zé) MySQL Server 與客戶端的通信,接受客戶端的命令請求首量,傳遞 Server 端的結(jié)果信息等壮吩。線程管理模塊則負(fù)責(zé)管理維護(hù)這些連接線程。包括線程的創(chuàng)建蕾总,線程的 cache 等
4粥航、SQL Interface: SQL接口
接受用戶的SQL命令,并且返回用戶需要查詢的結(jié)果生百。比如select from就是調(diào)用SQL Interface
5.Parser: 解析器
SQL命令傳遞到解析器的時候會被解析器驗證和解析递雀。
主要功能
a . 將SQL語句進(jìn)行語義和語法的分析,分解成數(shù)據(jù)結(jié)構(gòu)蚀浆,然后按照不同的操作類型進(jìn)行分類缀程,然后做出針對性的轉(zhuǎn)發(fā)到后續(xù)步驟,以后SQL語句的傳遞和處理就是基于這個結(jié)構(gòu)的市俊。
b. 如果在分解過程中遇到錯誤杨凑,那么就說明這個sql語句是不合理的。
6摆昧、Optimizer: 查詢優(yōu)化器
SQL語句在查詢之前會使用查詢優(yōu)化器對查詢進(jìn)行優(yōu)化撩满。explain語句查看的SQL語句執(zhí)行計劃,就是由查詢優(yōu)化器生成的绅你。
7伺帘、Cache和Buffer:查詢緩存
他的主要功能是將客戶端提交給MySQL的 select請求的返回結(jié)果集 cache 到內(nèi)存中,與該 query 的一個 hash 值 做一個對應(yīng)忌锯。該 Query 所取數(shù)據(jù)的基表發(fā)生任何數(shù)據(jù)的變化之后伪嫁, MySQL 會自動使該 query 的Cache 失效。在讀寫比例非常高的應(yīng)用系統(tǒng)中偶垮, Query Cache 對性能的提高是非常顯著的张咳。當(dāng)然它對內(nèi)存的消耗也是非常大的帝洪。如果查詢緩存有命中的查詢結(jié)果,查詢語句就可以直接去查詢緩存中取數(shù)據(jù)脚猾。這個緩存機(jī)制是由一系列小緩存組成的葱峡。比如表緩存,記錄緩存婚陪,key緩存族沃,權(quán)限緩存等
8、 Pluggable Storage Engines:存儲引擎
與其他數(shù)據(jù)庫例如Oracle 和SQL Server等數(shù)據(jù)庫中只有一種存儲引擎不同的是泌参,MySQL有一個被稱為“Pluggable Storage Engine Architecture”(可插拔的存儲引擎架構(gòu))的特性脆淹,也就意味著MySQL數(shù)據(jù)庫提供了多種存儲引擎。
而且存儲引擎是針對表的沽一,用戶可以根據(jù)不同的需求為數(shù)據(jù)表選擇不同的存儲引擎盖溺,用戶也可以根據(jù)自己的需要編寫自己的存儲引擎。也就是說铣缠,同一數(shù)據(jù)庫不同的表可以選擇不同的存儲引擎烘嘱。
簡單架構(gòu)流程圖:
二、為什么需要事務(wù)
- 分析update driver_info set driver_status = 2 where driver_id = 10001;這條語句執(zhí)行會有什么問題嗎?
先理解幾個概念:
1、數(shù)據(jù)庫數(shù)據(jù)存放的文件稱為data file
2梨与、日志文件稱為log file
數(shù)據(jù)庫數(shù)據(jù)是有緩存的,如果沒有緩存哮内,每次都寫或者讀物理disk,那性能就太低下了:
3壮韭、數(shù)據(jù)庫數(shù)據(jù)的緩存稱為data buffer
4北发、日志(redo)緩存稱為log buffer
既然數(shù)據(jù)庫數(shù)據(jù)有緩存,就很難保證緩存數(shù)據(jù)(臟數(shù)據(jù))與磁盤數(shù)據(jù)的一致性喷屋。如下圖
1到2的過程中一旦發(fā)生服務(wù)宕機(jī)或斷電琳拨,緩存數(shù)據(jù)會丟失,緩存與磁盤數(shù)據(jù)造成不一致問題屯曹。
在并發(fā)環(huán)境下狱庇,如果不做有效控制,會出現(xiàn)什么情況:
兩個客戶端在操作同一個塊數(shù)據(jù)的時候恶耽,從上圖中可以看出如果沒有處理好磁盤與緩存數(shù)據(jù)一致性的問題,那么讀到緩存的數(shù)據(jù)就會變成臟數(shù)據(jù)密任!緩存存在的意義是為了提供更好的查詢性能,如果連數(shù)據(jù)一致性都無法保證那么毫無意義驳棱!
事務(wù),最終的目的是為了保證數(shù)據(jù)的一致性
三农曲、事務(wù)特性ACID
- 原子性(Atomicity):事務(wù)中的所有操作作為一個整體像原子一樣不可分割社搅,要么全部成功,要么全部失敗驻债。
- 一致性(Consistency):事務(wù)的執(zhí)行結(jié)果必須使數(shù)據(jù)庫從一個一致性狀態(tài)到另一個一致性狀態(tài)。一致性狀態(tài)是指:
1.系統(tǒng)的狀態(tài)滿足數(shù)據(jù)的完整性約束(主碼,參照完整性,check約束等)
2.系統(tǒng)的狀態(tài)反應(yīng)數(shù)據(jù)庫本應(yīng)描述的現(xiàn)實世界的真實狀態(tài),比如轉(zhuǎn)賬前后兩個賬戶的金額總和應(yīng)該保持不變形葬。 - 隔離性(Isolation):并發(fā)執(zhí)行的事務(wù)不會相互影響,其對數(shù)據(jù)庫的影響和它們串行執(zhí)行時一樣合呐。比如多個用戶同時往一個賬戶轉(zhuǎn)賬,最后賬戶的結(jié)果應(yīng)該和他們按先后次序轉(zhuǎn)賬的結(jié)果一樣。
- 持久性(Durability):事務(wù)一旦提交,其對數(shù)據(jù)庫的更新就是持久的笙以。任何事務(wù)或系統(tǒng)故障都不會導(dǎo)致數(shù)據(jù)丟失淌实。
這里我們需要單獨把隔離性拿出來說下:
1、事務(wù)的隔離級別分為四種:
簡單敘述
- 臟讀:事務(wù)1讀到事務(wù)2未提交的數(shù)據(jù)猖腕。
- 不可重復(fù)讀:事務(wù)1第一次讀的數(shù)據(jù)和第二次讀的數(shù)據(jù)不一致拆祈,因為事務(wù)2改變數(shù)據(jù)并提交了。
- 幻讀:事務(wù)1第一次讀的數(shù)據(jù)行數(shù)和第二次讀的數(shù)據(jù)行數(shù)不一致倘感,因為事務(wù)2新增數(shù)據(jù)并提交了放坏。
事務(wù)的隔離級別是用利用鎖來控制的,Mysql默認(rèn)事務(wù)隔離級別是可重復(fù)讀(RR),但是利用MVCC控制版本來解決幻讀問題老玛。
2淤年、鎖
MySQL的行級鎖,是由存儲引擎來實現(xiàn)的蜡豹,這里我們主要講解InnoDB的行級鎖麸粮。
InnoDB的行級鎖,按照鎖定范圍來說镜廉,分為三種:
記錄鎖(Record Locks):鎖定索引中一條記錄弄诲。
間隙鎖(Gap Locks):要么鎖住索引記錄中間的值,要么鎖住第一個索引記錄前面的值或者最后一個索引記錄后面的值桨吊。
Next-Key Locks:是索引記錄上的記錄鎖和在索引記錄之前的間隙鎖的組合威根。
InnoDB的行級鎖,按照功能來說视乐,分為兩種:
- 共享鎖(S):允許一個事務(wù)去讀一行洛搀,阻止其他事務(wù)獲得相同數(shù)據(jù)集的排他鎖。
- 排他鎖(X):允許獲得排他鎖的事務(wù)更新數(shù)據(jù)佑淀,阻止其他事務(wù)取得相同數(shù)據(jù)集的共享讀鎖和排他寫鎖留美。
對于UPDATE、DELETE和INSERT語句伸刃,InnoDB會自動給涉及數(shù)據(jù)集加排他鎖(X)谎砾;對于普通SELECT語句,InnoDB不會加任何鎖捧颅,事務(wù)可以通過以下語句顯示給記錄集加共享鎖或排他鎖景图。
手動添加共享鎖(S):
SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE
手動添加排他鎖(x):
SELECT * FROM table_name WHERE ... FOR UPDATE
InnoDB也實現(xiàn)了表級鎖,也就是意向鎖碉哑,意向鎖是mysql內(nèi)部使用的挚币,不需要用戶干預(yù)亮蒋。
- 意向共享鎖(IS):事務(wù)打算給數(shù)據(jù)行加行共享鎖,事務(wù)在給一個數(shù)據(jù)行加共享鎖前必須先取得該表的IS鎖妆毕。
-
意向排他鎖(IX):事務(wù)打算給數(shù)據(jù)行加行排他鎖慎玖,事務(wù)在給一個數(shù)據(jù)行加排他鎖前必須先取得該表的IX鎖。
意向鎖和行鎖可以共存笛粘,意向鎖的主要作用是為了【全表更新數(shù)據(jù)】時的性能提升趁怔。否則在全表更新數(shù)據(jù)時,需要先檢索該范是否某些記錄上面有行鎖薪前。
image.png
InnoDB行鎖是通過給索引上的索引項加鎖來實現(xiàn)的润努,因此InnoDB這種行鎖實現(xiàn)特點意味著:只有通過索引條件檢索的數(shù)據(jù),InnoDB才使用行級鎖序六,否則任连,InnoDB將使用表鎖!
3例诀、MVCC和一致性非鎖定讀
數(shù)據(jù)庫的并發(fā)控制機(jī)制有很多随抠,最為常見的就是鎖機(jī)制(事務(wù)開始時DQL加讀鎖結(jié)束時釋放讀鎖、同理給DML加寫鎖)繁涂。鎖機(jī)制一般會給競爭資源加鎖拱她,阻塞讀或者寫操作來解決事務(wù)之間的競爭條件,最終保證事務(wù)的可串行化扔罪。
而MVCC則引入了另外一種并發(fā)控制秉沼,它讓讀寫操作互不阻塞,每一個寫操作都會創(chuàng)建一個新版本的數(shù)據(jù)矿酵,讀操作會從有限多個版本的數(shù)據(jù)中挑選一個最合適的結(jié)果直接返回唬复,由此解決了事務(wù)的競爭條件。
上圖直觀地展現(xiàn)了InnoDB一致性非鎖定讀的機(jī)制全肮。之所以稱其為非鎖定讀敞咧,是因為不需要等待行上排他鎖的釋放」枷伲快照數(shù)據(jù)是指該行的之前版本的數(shù)據(jù)休建,每行記錄可能有多個版本,一般稱這種技術(shù)為行多版本技術(shù)评疗。由此帶來的并發(fā)控制测砂,稱之為多版本并發(fā)控制(Multi Version Concurrency Control, MVCC)。InnoDB是通過undo log來實現(xiàn)MVCC百匆。
在事務(wù)隔離級別READ COMMITTED和REPEATABLE READ下砌些,InnoDB默認(rèn)使用一致性非鎖定讀。然而加匈,對于快照數(shù)據(jù)的定義卻不同存璃。在READ COMMITTED事務(wù)隔離級別下宙彪,一致性非鎖定讀總是讀取被鎖定行的最新一份快照數(shù)據(jù)。而在REPEATABLE READ事務(wù)隔離級別下有巧,則讀取事務(wù)開始時的行數(shù)據(jù)版本。
MVCC使得數(shù)據(jù)庫讀不會對數(shù)據(jù)加鎖悲没,普通的SELECT請求不會加鎖篮迎,提高了數(shù)據(jù)庫的并發(fā)處理能力。借助MVCC示姿,數(shù)據(jù)庫可以實現(xiàn)READ COMMITTED甜橱,REPEATABLE READ等隔離級別,用戶可以查看當(dāng)前數(shù)據(jù)的前一個或者前幾個歷史版本栈戳,保證了ACID中的I特性(隔離性)
四岂傲、理解幾個問題
- 怎么實現(xiàn)回滾?
簡單理解子檀,如果有個迷宮你想要通過它镊掖,但是你也不知道能不能通過,當(dāng)你走到半途中發(fā)現(xiàn)自己無法再走下去的時候你要返回去褂痰,那你該怎么返回呢亩进,是不是應(yīng)該在進(jìn)入的途中走一步就在墻上標(biāo)記“回去的方向”
(undo log) - 在事務(wù)提交入庫的過程中尚有臟頁未寫入磁盤,這時服務(wù)發(fā)生故障時該怎么辦缩歪?(redo log)
- 多個事務(wù)存在的情況归薛,怎么管理好事務(wù)間的隔離關(guān)系?
(通過鎖和MVCC多版本控制來實現(xiàn)匪蝙,其中MVCC可以解決RR隔離級別下 幻讀的問題)
五主籍、引入事務(wù)日志
事務(wù)日志是在存儲引擎層面的,像bin log是mysql server層面的日志,不能混淆逛球。
1千元、回滾日志undo log
作用: 保存了事務(wù)發(fā)生之前的數(shù)據(jù)的一個版本,可以用于回滾需忿,同時可以提供多版本并發(fā)控制下的讀(MVCC)诅炉,也即非鎖定讀
內(nèi)容: 邏輯格式的日志,在執(zhí)行undo的時候屋厘,僅僅是將數(shù)據(jù)從邏輯上恢復(fù)至事務(wù)之前的狀態(tài)涕烧,而不是從物理頁面上操作實現(xiàn)的,這一點是不同于redo log的汗洒。
什么時候產(chǎn)生: 事務(wù)開始之前议纯,將當(dāng)前是的版本生成undo log,undo 也會產(chǎn)生 redo 來保證undo log的可靠性
什么時候釋放: 當(dāng)事務(wù)提交之后溢谤,undo log并不能立馬被刪除瞻凤,而是放入待清理的鏈表憨攒,由purge線程判斷是否由其他事務(wù)在使用undo段中表的上一個事務(wù)之前的版本信息,決定是否可以清理undo log的日志空間
2阀参、重做日志(redo log)
作用: 確保事務(wù)的持久性肝集。 防止在發(fā)生故障的時間點,尚有臟頁未寫入磁盤蛛壳,在重啟mysql服務(wù)的時候杏瞻,根據(jù)redo log進(jìn)行重做,從而達(dá)到事務(wù)的持久性這一特性衙荐。
內(nèi)容: 物理格式的日志捞挥,記錄的是事務(wù)開始執(zhí)行修改后的數(shù)據(jù),其redo log是順序?qū)懭雛edo log file的物理文件中去的忧吟。
什么時候產(chǎn)生: 事務(wù)開始之后就產(chǎn)生redo log砌函,redo log的落盤并不是隨著事務(wù)的提交才寫入的,而是在事務(wù)的執(zhí)行過程中溜族,便開始寫入redo log文件中讹俊。
什么時候釋放: 當(dāng)對應(yīng)事務(wù)的臟頁寫入到磁盤之后,redo log的使命也就完成了煌抒,重做日志占用的空間就可以重用(被覆蓋)劣像。
參考資料:https://blog.csdn.net/u010002184/article/details/88526708