萬(wàn)事皆由不是空穴來(lái)風(fēng),任何技術(shù)的底層都有一套嚴(yán)謹(jǐn)?shù)募軜?gòu)。
前景
說(shuō)到事務(wù),我們都知道“回滾”這個(gè)概念,在一個(gè)方法上加上@Transactional 一旦程序遇到異常就會(huì)自動(dòng)回滾,數(shù)據(jù)庫(kù)的數(shù)據(jù)原封不動(dòng),到底是怎么實(shí)現(xiàn)回滾機(jī)制?其中 isolation隔離級(jí)別又是干什么?讓我們一起從Mysql底層架構(gòu)開(kāi)始談起。
一、Mysql架構(gòu)分析
首先看下邏輯架構(gòu)圖,

初學(xué)Mysql這個(gè)架構(gòu)圖放在眼前有點(diǎn)懵比了吧,每個(gè)模塊我們分析來(lái)看:
1、Connectors 連接器,指的是不同語(yǔ)言中與SQL的交互
2、Management Serveices & Utilities:系統(tǒng)管理和控制工具
3、Connection Pool: 連接池
1)管理緩沖用戶連接,線程處理等需要緩存的需求。
2)負(fù)責(zé)監(jiān)聽(tīng)對(duì) MySQL Server 的各種請(qǐng)求,接收連接請(qǐng)求,轉(zhuǎn)發(fā)所有連接請(qǐng)求到線程管理模塊。每一個(gè)連接上 MySQL Server 的客戶端請(qǐng)求都會(huì)被分配(或創(chuàng)建)一個(gè)連接線程為其單獨(dú)服務(wù)。
3)而連接線程的主要工作就是負(fù)責(zé) MySQL Server 與客戶端的通信,接受客戶端的命令請(qǐng)求,傳遞 Server 端的結(jié)果信息等。線程管理模塊則負(fù)責(zé)管理維護(hù)這些連接線程。包括線程的創(chuàng)建,線程的 cache 等
4、SQL Interface: SQL接口
接受用戶的SQL命令,并且返回用戶需要查詢的結(jié)果。比如select from就是調(diào)用SQL Interface
5.Parser: 解析器
SQL命令傳遞到解析器的時(shí)候會(huì)被解析器驗(yàn)證和解析。
主要功能
a . 將SQL語(yǔ)句進(jìn)行語(yǔ)義和語(yǔ)法的分析,分解成數(shù)據(jù)結(jié)構(gòu),然后按照不同的操作類型進(jìn)行分類,然后做出針對(duì)性的轉(zhuǎn)發(fā)到后續(xù)步驟,以后SQL語(yǔ)句的傳遞和處理就是基于這個(gè)結(jié)構(gòu)的。
b. 如果在分解過(guò)程中遇到錯(cuò)誤,那么就說(shuō)明這個(gè)sql語(yǔ)句是不合理的。
6、Optimizer: 查詢優(yōu)化器
SQL語(yǔ)句在查詢之前會(huì)使用查詢優(yōu)化器對(duì)查詢進(jìn)行優(yōu)化。explain語(yǔ)句查看的SQL語(yǔ)句執(zhí)行計(jì)劃,就是由查詢優(yōu)化器生成的。
7、Cache和Buffer:查詢緩存
他的主要功能是將客戶端提交給MySQL的 select請(qǐng)求的返回結(jié)果集 cache 到內(nèi)存中,與該 query 的一個(gè) hash 值 做一個(gè)對(duì)應(yīng)。該 Query 所取數(shù)據(jù)的基表發(fā)生任何數(shù)據(jù)的變化之后, MySQL 會(huì)自動(dòng)使該 query 的Cache 失效。在讀寫(xiě)比例非常高的應(yīng)用系統(tǒng)中, Query Cache 對(duì)性能的提高是非常顯著的。當(dāng)然它對(duì)內(nèi)存的消耗也是非常大的。如果查詢緩存有命中的查詢結(jié)果,查詢語(yǔ)句就可以直接去查詢緩存中取數(shù)據(jù)。這個(gè)緩存機(jī)制是由一系列小緩存組成的。比如表緩存,記錄緩存,key緩存,權(quán)限緩存等
8、 Pluggable Storage Engines:存儲(chǔ)引擎
與其他數(shù)據(jù)庫(kù)例如Oracle 和SQL Server等數(shù)據(jù)庫(kù)中只有一種存儲(chǔ)引擎不同的是,MySQL有一個(gè)被稱為“Pluggable Storage Engine Architecture”(可插拔的存儲(chǔ)引擎架構(gòu))的特性,也就意味著MySQL數(shù)據(jù)庫(kù)提供了多種存儲(chǔ)引擎。
而且存儲(chǔ)引擎是針對(duì)表的,用戶可以根據(jù)不同的需求為數(shù)據(jù)表選擇不同的存儲(chǔ)引擎,用戶也可以根據(jù)自己的需要編寫(xiě)自己的存儲(chǔ)引擎。也就是說(shuō),同一數(shù)據(jù)庫(kù)不同的表可以選擇不同的存儲(chǔ)引擎。
簡(jiǎn)單架構(gòu)流程圖:

二、為什么需要事務(wù)
- 分析update driver_info set driver_status = 2 where driver_id = 10001;這條語(yǔ)句執(zhí)行會(huì)有什么問(wèn)題嗎?
先理解幾個(gè)概念:
1、數(shù)據(jù)庫(kù)數(shù)據(jù)存放的文件稱為data file
2、日志文件稱為log file
數(shù)據(jù)庫(kù)數(shù)據(jù)是有緩存的,如果沒(méi)有緩存,每次都寫(xiě)或者讀物理disk,那性能就太低下了:
3、數(shù)據(jù)庫(kù)數(shù)據(jù)的緩存稱為data buffer
4、日志(redo)緩存稱為log buffer
既然數(shù)據(jù)庫(kù)數(shù)據(jù)有緩存,就很難保證緩存數(shù)據(jù)(臟數(shù)據(jù))與磁盤(pán)數(shù)據(jù)的一致性。如下圖

1到2的過(guò)程中一旦發(fā)生服務(wù)宕機(jī)或斷電,緩存數(shù)據(jù)會(huì)丟失,緩存與磁盤(pán)數(shù)據(jù)造成不一致問(wèn)題。
在并發(fā)環(huán)境下,如果不做有效控制,會(huì)出現(xiàn)什么情況:

兩個(gè)客戶端在操作同一個(gè)塊數(shù)據(jù)的時(shí)候,從上圖中可以看出如果沒(méi)有處理好磁盤(pán)與緩存數(shù)據(jù)一致性的問(wèn)題,那么讀到緩存的數(shù)據(jù)就會(huì)變成臟數(shù)據(jù)!緩存存在的意義是為了提供更好的查詢性能,如果連數(shù)據(jù)一致性都無(wú)法保證那么毫無(wú)意義!
事務(wù),最終的目的是為了保證數(shù)據(jù)的一致性
三、事務(wù)特性ACID
- 原子性(Atomicity):事務(wù)中的所有操作作為一個(gè)整體像原子一樣不可分割,要么全部成功,要么全部失敗。
- 一致性(Consistency):事務(wù)的執(zhí)行結(jié)果必須使數(shù)據(jù)庫(kù)從一個(gè)一致性狀態(tài)到另一個(gè)一致性狀態(tài)。一致性狀態(tài)是指:
1.系統(tǒng)的狀態(tài)滿足數(shù)據(jù)的完整性約束(主碼,參照完整性,check約束等)
2.系統(tǒng)的狀態(tài)反應(yīng)數(shù)據(jù)庫(kù)本應(yīng)描述的現(xiàn)實(shí)世界的真實(shí)狀態(tài),比如轉(zhuǎn)賬前后兩個(gè)賬戶的金額總和應(yīng)該保持不變。 - 隔離性(Isolation):并發(fā)執(zhí)行的事務(wù)不會(huì)相互影響,其對(duì)數(shù)據(jù)庫(kù)的影響和它們串行執(zhí)行時(shí)一樣。比如多個(gè)用戶同時(shí)往一個(gè)賬戶轉(zhuǎn)賬,最后賬戶的結(jié)果應(yīng)該和他們按先后次序轉(zhuǎn)賬的結(jié)果一樣。
- 持久性(Durability):事務(wù)一旦提交,其對(duì)數(shù)據(jù)庫(kù)的更新就是持久的。任何事務(wù)或系統(tǒng)故障都不會(huì)導(dǎo)致數(shù)據(jù)丟失。
這里我們需要單獨(dú)把隔離性拿出來(lái)說(shuō)下:
1、事務(wù)的隔離級(jí)別分為四種:

簡(jiǎn)單敘述
- 臟讀:事務(wù)1讀到事務(wù)2未提交的數(shù)據(jù)。
- 不可重復(fù)讀:事務(wù)1第一次讀的數(shù)據(jù)和第二次讀的數(shù)據(jù)不一致,因?yàn)槭聞?wù)2改變數(shù)據(jù)并提交了。
- 幻讀:事務(wù)1第一次讀的數(shù)據(jù)行數(shù)和第二次讀的數(shù)據(jù)行數(shù)不一致,因?yàn)槭聞?wù)2新增數(shù)據(jù)并提交了。
事務(wù)的隔離級(jí)別是用利用鎖來(lái)控制的,Mysql默認(rèn)事務(wù)隔離級(jí)別是可重復(fù)讀(RR),但是利用MVCC控制版本來(lái)解決幻讀問(wèn)題。
2、鎖
MySQL的行級(jí)鎖,是由存儲(chǔ)引擎來(lái)實(shí)現(xiàn)的,這里我們主要講解InnoDB的行級(jí)鎖。
InnoDB的行級(jí)鎖,按照鎖定范圍來(lái)說(shuō),分為三種:
記錄鎖(Record Locks):鎖定索引中一條記錄。
間隙鎖(Gap Locks):要么鎖住索引記錄中間的值,要么鎖住第一個(gè)索引記錄前面的值或者最后一個(gè)索引記錄后面的值。
Next-Key Locks:是索引記錄上的記錄鎖和在索引記錄之前的間隙鎖的組合。
InnoDB的行級(jí)鎖,按照功能來(lái)說(shuō),分為兩種:
- 共享鎖(S):允許一個(gè)事務(wù)去讀一行,阻止其他事務(wù)獲得相同數(shù)據(jù)集的排他鎖。
- 排他鎖(X):允許獲得排他鎖的事務(wù)更新數(shù)據(jù),阻止其他事務(wù)取得相同數(shù)據(jù)集的共享讀鎖和排他寫(xiě)鎖。
對(duì)于UPDATE、DELETE和INSERT語(yǔ)句,InnoDB會(huì)自動(dòng)給涉及數(shù)據(jù)集加排他鎖(X);對(duì)于普通SELECT語(yǔ)句,InnoDB不會(huì)加任何鎖,事務(wù)可以通過(guò)以下語(yǔ)句顯示給記錄集加共享鎖或排他鎖。
手動(dòng)添加共享鎖(S):
SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE
手動(dòng)添加排他鎖(x):
SELECT * FROM table_name WHERE ... FOR UPDATE
InnoDB也實(shí)現(xiàn)了表級(jí)鎖,也就是意向鎖,意向鎖是mysql內(nèi)部使用的,不需要用戶干預(yù)。
- 意向共享鎖(IS):事務(wù)打算給數(shù)據(jù)行加行共享鎖,事務(wù)在給一個(gè)數(shù)據(jù)行加共享鎖前必須先取得該表的IS鎖。
-
意向排他鎖(IX):事務(wù)打算給數(shù)據(jù)行加行排他鎖,事務(wù)在給一個(gè)數(shù)據(jù)行加排他鎖前必須先取得該表的IX鎖。
意向鎖和行鎖可以共存,意向鎖的主要作用是為了【全表更新數(shù)據(jù)】時(shí)的性能提升。否則在全表更新數(shù)據(jù)時(shí),需要先檢索該范是否某些記錄上面有行鎖。
image.png
InnoDB行鎖是通過(guò)給索引上的索引項(xiàng)加鎖來(lái)實(shí)現(xiàn)的,因此InnoDB這種行鎖實(shí)現(xiàn)特點(diǎn)意味著:只有通過(guò)索引條件檢索的數(shù)據(jù),InnoDB才使用行級(jí)鎖,否則,InnoDB將使用表鎖!
3、MVCC和一致性非鎖定讀
數(shù)據(jù)庫(kù)的并發(fā)控制機(jī)制有很多,最為常見(jiàn)的就是鎖機(jī)制(事務(wù)開(kāi)始時(shí)DQL加讀鎖結(jié)束時(shí)釋放讀鎖、同理給DML加寫(xiě)鎖)。鎖機(jī)制一般會(huì)給競(jìng)爭(zhēng)資源加鎖,阻塞讀或者寫(xiě)操作來(lái)解決事務(wù)之間的競(jìng)爭(zhēng)條件,最終保證事務(wù)的可串行化。
而MVCC則引入了另外一種并發(fā)控制,它讓讀寫(xiě)操作互不阻塞,每一個(gè)寫(xiě)操作都會(huì)創(chuàng)建一個(gè)新版本的數(shù)據(jù),讀操作會(huì)從有限多個(gè)版本的數(shù)據(jù)中挑選一個(gè)最合適的結(jié)果直接返回,由此解決了事務(wù)的競(jìng)爭(zhēng)條件。

上圖直觀地展現(xiàn)了InnoDB一致性非鎖定讀的機(jī)制。之所以稱其為非鎖定讀,是因?yàn)椴恍枰却猩吓潘i的釋放??煺諗?shù)據(jù)是指該行的之前版本的數(shù)據(jù),每行記錄可能有多個(gè)版本,一般稱這種技術(shù)為行多版本技術(shù)。由此帶來(lái)的并發(fā)控制,稱之為多版本并發(fā)控制(Multi Version Concurrency Control, MVCC)。InnoDB是通過(guò)undo log來(lái)實(shí)現(xiàn)MVCC。
在事務(wù)隔離級(jí)別READ COMMITTED和REPEATABLE READ下,InnoDB默認(rèn)使用一致性非鎖定讀。然而,對(duì)于快照數(shù)據(jù)的定義卻不同。在READ COMMITTED事務(wù)隔離級(jí)別下,一致性非鎖定讀總是讀取被鎖定行的最新一份快照數(shù)據(jù)。而在REPEATABLE READ事務(wù)隔離級(jí)別下,則讀取事務(wù)開(kāi)始時(shí)的行數(shù)據(jù)版本。
MVCC使得數(shù)據(jù)庫(kù)讀不會(huì)對(duì)數(shù)據(jù)加鎖,普通的SELECT請(qǐng)求不會(huì)加鎖,提高了數(shù)據(jù)庫(kù)的并發(fā)處理能力。借助MVCC,數(shù)據(jù)庫(kù)可以實(shí)現(xiàn)READ COMMITTED,REPEATABLE READ等隔離級(jí)別,用戶可以查看當(dāng)前數(shù)據(jù)的前一個(gè)或者前幾個(gè)歷史版本,保證了ACID中的I特性(隔離性)
四、理解幾個(gè)問(wèn)題
- 怎么實(shí)現(xiàn)回滾?
簡(jiǎn)單理解,如果有個(gè)迷宮你想要通過(guò)它,但是你也不知道能不能通過(guò),當(dāng)你走到半途中發(fā)現(xiàn)自己無(wú)法再走下去的時(shí)候你要返回去,那你該怎么返回呢,是不是應(yīng)該在進(jìn)入的途中走一步就在墻上標(biāo)記“回去的方向”
(undo log) - 在事務(wù)提交入庫(kù)的過(guò)程中尚有臟頁(yè)未寫(xiě)入磁盤(pán),這時(shí)服務(wù)發(fā)生故障時(shí)該怎么辦?(redo log)
- 多個(gè)事務(wù)存在的情況,怎么管理好事務(wù)間的隔離關(guān)系?
(通過(guò)鎖和MVCC多版本控制來(lái)實(shí)現(xiàn),其中MVCC可以解決RR隔離級(jí)別下 幻讀的問(wèn)題)
五、引入事務(wù)日志
事務(wù)日志是在存儲(chǔ)引擎層面的,像bin log是mysql server層面的日志,不能混淆。
1、回滾日志undo log
作用: 保存了事務(wù)發(fā)生之前的數(shù)據(jù)的一個(gè)版本,可以用于回滾,同時(shí)可以提供多版本并發(fā)控制下的讀(MVCC),也即非鎖定讀
內(nèi)容: 邏輯格式的日志,在執(zhí)行undo的時(shí)候,僅僅是將數(shù)據(jù)從邏輯上恢復(fù)至事務(wù)之前的狀態(tài),而不是從物理頁(yè)面上操作實(shí)現(xiàn)的,這一點(diǎn)是不同于redo log的。
什么時(shí)候產(chǎn)生: 事務(wù)開(kāi)始之前,將當(dāng)前是的版本生成undo log,undo 也會(huì)產(chǎn)生 redo 來(lái)保證undo log的可靠性
什么時(shí)候釋放: 當(dāng)事務(wù)提交之后,undo log并不能立馬被刪除,而是放入待清理的鏈表,由purge線程判斷是否由其他事務(wù)在使用undo段中表的上一個(gè)事務(wù)之前的版本信息,決定是否可以清理undo log的日志空間
2、重做日志(redo log)
作用: 確保事務(wù)的持久性。 防止在發(fā)生故障的時(shí)間點(diǎn),尚有臟頁(yè)未寫(xiě)入磁盤(pán),在重啟mysql服務(wù)的時(shí)候,根據(jù)redo log進(jìn)行重做,從而達(dá)到事務(wù)的持久性這一特性。
內(nèi)容: 物理格式的日志,記錄的是事務(wù)開(kāi)始執(zhí)行修改后的數(shù)據(jù),其redo log是順序?qū)懭雛edo log file的物理文件中去的。
什么時(shí)候產(chǎn)生: 事務(wù)開(kāi)始之后就產(chǎn)生redo log,redo log的落盤(pán)并不是隨著事務(wù)的提交才寫(xiě)入的,而是在事務(wù)的執(zhí)行過(guò)程中,便開(kāi)始寫(xiě)入redo log文件中。
什么時(shí)候釋放: 當(dāng)對(duì)應(yīng)事務(wù)的臟頁(yè)寫(xiě)入到磁盤(pán)之后,redo log的使命也就完成了,重做日志占用的空間就可以重用(被覆蓋)。
參考資料:https://blog.csdn.net/u010002184/article/details/88526708
