一直在使用mysql, 但是對(duì)于mysql 的一些核心原理一直有些模糊。出問(wèn)題總是想著公司有專職的DBA, 專業(yè)的事情交給專業(yè)的人來(lái)做嘛,所以一直沒(méi)有研究(其實(shí)主要是懶)。趁著假期,閑著也是閑著,就想簡(jiǎn)單了解一下。人嘛,總是嫌麻煩,總想找些現(xiàn)成的文章或者課程。找了極客時(shí)間的兩套mysql相關(guān)課程 (基礎(chǔ))SQL必知必會(huì)、(進(jìn)階)MySQL實(shí)戰(zhàn)45講,看了個(gè)大概,只總結(jié)出來(lái)mysql最核心的應(yīng)該是事務(wù)跟索引。
但是看完之后,思維陷入了混亂,每一節(jié)都是一些知識(shí)點(diǎn)的堆積,沒(méi)有形成體系,自己想不明白彼此之間到底是什么關(guān)系。自己又找了拉鉤上的《高性能mysql實(shí)戰(zhàn)》,有個(gè)大概了。我整理了兩個(gè)核心問(wèn)題 :1、“事務(wù)的特性是如何實(shí)現(xiàn)的?”;2、“事務(wù)四種隔離級(jí)別是怎么結(jié)合鎖來(lái)解決并發(fā)產(chǎn)生的三種經(jīng)典問(wèn)題的?” 。
本文是mysql 事務(wù)的第一部分-四個(gè)特性及實(shí)現(xiàn)。自己不是專職的DBA,理解難免有偏差,只是簡(jiǎn)單地總結(jié)一下。希望對(duì)你有所幫助,如果有紕漏,也請(qǐng)留言指出,感謝~
文章主要內(nèi)容如下:
- 事務(wù)的定義
- 事務(wù)的四個(gè)特性以及背后的技術(shù)原理
1 事務(wù)是什么
做工程實(shí)現(xiàn),想必大家都接觸過(guò)事務(wù)。事務(wù),一組邏輯執(zhí)行單元,要么全部做完,要不都不做,不會(huì)處于一個(gè)中間狀態(tài)。事務(wù)是為了保證DB操作正確性跟完整性的保障。
2 事務(wù)的四個(gè)特性
按照上一小節(jié)的描述,更多的像是在描述事務(wù)原子性這一特性。其實(shí)事務(wù)并不僅僅是只有原子性這一個(gè)特性。是由四部分組成 ACID。下面我們介紹一下這部分:
2.1 原子性(Atomicity)
原子性:事務(wù)的所有操作,要么全部完成,要么全部不完成,不會(huì)結(jié)束在某個(gè)中間環(huán)節(jié)。
這個(gè)是最基本的要求。
2.2 一致性(Consistency)
一致性:事務(wù)完成之后,事務(wù)所做的修改進(jìn)行持久化保存,不會(huì)丟失。
一致性更像是其他三個(gè)特性綜合之后的結(jié)果。需要依賴于它們。一致性可以從兩個(gè)角度思考:一個(gè)是約束的一致性(比如主鍵、外鍵約束),另一種是業(yè)務(wù)邏輯上的一致性。
2.3 隔離性(Isolation)
隔離性:當(dāng)多個(gè)事務(wù)并發(fā)訪問(wèn)數(shù)據(jù)庫(kù)中的同一數(shù)據(jù)時(shí),所表現(xiàn)出來(lái)的相互關(guān)系。
隔離性關(guān)系到多個(gè)事務(wù)并發(fā)操作時(shí)數(shù)據(jù)的準(zhǔn)確性。innodb有四個(gè)常見(jiàn)的隔離性,這些隔離性會(huì)借助于鎖機(jī)制 解決一些并發(fā)常見(jiàn)的問(wèn)題。當(dāng)然,因?yàn)楦綦x性強(qiáng)弱的不同,有些問(wèn)題在某些場(chǎng)景下是解決不了的。下一節(jié),我們將重點(diǎn)講解這個(gè)問(wèn)題。
2.4 持久性(Durability)
持久性:事務(wù)開(kāi)始之前和事務(wù)結(jié)束之后,數(shù)據(jù)庫(kù)的完整性限制未被破壞。
這兒主要是指持久化的問(wèn)題。數(shù)據(jù)寫入之后,要保證不會(huì)丟失,在系統(tǒng)崩潰之后,也能恢復(fù)。
3 四個(gè)特性背后是怎么實(shí)現(xiàn)的?
大多數(shù)的文章,講完事務(wù)的四個(gè)特性就結(jié)束了。如果只是停留在這個(gè)層面,我們就沒(méi)有寫這篇文章的必要了。在這部分,我們會(huì)講解一下四個(gè)特性是如何實(shí)現(xiàn)的?在講解特性實(shí)現(xiàn)原理之前,我們先講一下用到的幾個(gè)知識(shí)點(diǎn)。
3.1 redo、undo與binlog
3.1.1 redo
redo, 重做日志,保證了事務(wù)的原子性與持久性。通常是物理日志,用于恢復(fù)事務(wù)修改的頁(yè)操作。
redo 包括兩部分:
- 1 內(nèi)存中的重做日志緩沖,易失,一般在事務(wù)開(kāi)始時(shí),就要寫入這部分
- 2 第二部分是重做日志,這個(gè)是要刷如磁盤的。一般在事務(wù)提交時(shí)會(huì)刷如磁盤。有些場(chǎng)景,也可以設(shè)置沒(méi)用每次事務(wù)提交就刷,而是交給master thread 定期刷盤,但是這樣如果在這期間,宕機(jī)了,則沒(méi)有刷盤的數(shù)據(jù)就要丟失了。
redo 日志里面存儲(chǔ)的是操作指令,每個(gè)指令根據(jù)操作類型的不同,存儲(chǔ)格式也是不同的。
redo 在內(nèi)存中,結(jié)合了checkpoint 以及LSN(log sequence number) 來(lái)保證刷入磁盤的數(shù)據(jù)不丟。checkpoint記錄了刷磁盤刷到哪兒了,LSN記錄了當(dāng)前內(nèi)存中的日志編號(hào)。二者之間的內(nèi)容就是沒(méi)有刷新到磁盤的日志。
3.1.2 undo
undo保證了事務(wù)的一致性。undo 并不是物理日志,而是邏輯上的日志,有一個(gè)專門的內(nèi)存字段存儲(chǔ)undo 日志。
undo 主要有兩個(gè)作用:1是回滾操作,將數(shù)據(jù)庫(kù)邏輯地恢復(fù)到原來(lái)的樣子,但是已經(jīng)分配的頁(yè)、或者數(shù)據(jù)結(jié)果可能回滾不了了; 2 是MVCC(多版本并發(fā)控制),實(shí)現(xiàn)并發(fā)控制的一種很通用的機(jī)制,在下面的一節(jié)中,我們會(huì)講到。
當(dāng)事務(wù)提交的時(shí)候,innodb不會(huì)立即刪除undo log,因?yàn)楹罄m(xù)還可能會(huì)用到undo log,如隔離級(jí)別為repeatable read時(shí),事務(wù)讀取的都是開(kāi)啟事務(wù)時(shí)的最新提交行版本,只要該事務(wù)不結(jié)束,該行版本就不能刪除,即undo log不能刪除。
但是在事務(wù)提交的時(shí)候,會(huì)將該事務(wù)對(duì)應(yīng)的undo log放入到刪除列表中,未來(lái)通過(guò)purge來(lái)刪除。并且提交事務(wù)時(shí),還會(huì)判斷undo log分配的頁(yè)是否可以重用,如果可以重用,則會(huì)分配給后面來(lái)的事務(wù),避免為每個(gè)獨(dú)立的事務(wù)分配獨(dú)立的undo log頁(yè)而浪費(fèi)存儲(chǔ)空間和性能。
3.1.3 binlog
binlog基本定義:二進(jìn)制日志,也成為二進(jìn)制日志,記錄對(duì)數(shù)據(jù)發(fā)生或潛在發(fā)生更改的SQL語(yǔ)句,并以二進(jìn)制的形式保存在磁盤中;
作用:Mysql的作用類似于ORACLE的歸檔日志,可以用來(lái)查看數(shù)據(jù)庫(kù)的變更歷史(具體的時(shí)間點(diǎn)所有的SQL操作)、數(shù)據(jù)庫(kù)增量備份和恢復(fù)(增量備份和基于時(shí)間點(diǎn)的恢復(fù))、Mysql的復(fù)制(主主數(shù)據(jù)庫(kù)的復(fù)制、主從數(shù)據(jù)庫(kù)的復(fù)制)
3.1.4 更新操作中 redolog 與 binlog的順序

3.2 WAL (write ahead logging) 又稱 ARIES三原則
ARIES三原則,是指write ahead logging。
1 先寫日之后寫磁盤,日志成功寫入后就不會(huì)丟失,后續(xù)由checkpoint機(jī)制來(lái)保證磁盤物理文件與redo日志達(dá)到一致性。
2 利用Redo 記錄變更后的數(shù)據(jù),即redo記錄事務(wù)數(shù)據(jù)變更后的值
3 利用Undo 記錄變更前的數(shù)據(jù),用于回滾和其他事務(wù)多版本讀。
3.3 特性的實(shí)現(xiàn)
3.3.1 各特性間的關(guān)系
在講解具體實(shí)現(xiàn)之前,我們先來(lái)張圖,參考自 《高性能mysql實(shí)戰(zhàn)》

3.3.2 原子性的實(shí)現(xiàn)
每一個(gè)寫事務(wù),都會(huì)修改 Buffer Pool,從而產(chǎn)生相應(yīng)的 Redo 日志,這些日志信息會(huì)被記錄到 ib_logfiles 文件中。因?yàn)?Redo 日志是遵循 Write Ahead Log 的方式寫的,所以事務(wù)是順序被記錄的。
任何 Buffer Pool 中的頁(yè)被刷到磁盤之前,都會(huì)先寫入到日志文件中。
回滾(undo日志)
要保證原子性,就必須在異常發(fā)生時(shí),對(duì)已經(jīng)執(zhí)行的操作進(jìn)行回滾,此時(shí)就用到了undo 日志。
未刷盤數(shù)據(jù)提交(redo日志)
除了回滾之外,還有一種場(chǎng)景是事務(wù)提交了,日志寫入到buffer pool 了,但是buffer pool的臟頁(yè) 并沒(méi)有刷盤,那此時(shí)怎么恢復(fù)呢?就需要用到redo日志恢復(fù)數(shù)據(jù)。
綜合上述兩種case,其實(shí)原子性的保證就是用到了WAL的原則。
3.3.3 持久性的實(shí)現(xiàn)
持久性是表示一個(gè)事務(wù)一旦提交,它對(duì)數(shù)據(jù)的改變就是永久的。通過(guò)原子性可以保證的一旦事務(wù)提交,即使遇到宕機(jī),也可以從邏輯上將數(shù)據(jù)找回來(lái),再次寫入到物理存儲(chǔ)空間。
因?yàn)閞edo日志是有限的,那么宕機(jī)之后,redo日志之前的數(shù)據(jù)怎么恢復(fù)呢,這就結(jié)合binlog日志。
3.3.4 隔離性的實(shí)現(xiàn)
innodb隔離性有四種,我們簡(jiǎn)單看一下四種隔離級(jí)別都是怎么實(shí)現(xiàn)的。下一篇文章中會(huì)詳細(xì)介紹。
1、讀未提交:沒(méi)做任何控制。能夠讀到一個(gè)事務(wù)中的中間狀態(tài),是違背ACID的,所以在MySQL中基本不用。
2、讀已提交(RC):通過(guò)對(duì)數(shù)據(jù)加了寫鎖,在寫的過(guò)程中數(shù)據(jù)是不能被其他事務(wù)看到的。但是會(huì)存在不可重復(fù)讀的問(wèn)題。
3、可重復(fù)讀(RR):通過(guò)增加間隙鎖,解決了不可重讀的問(wèn)題,但是并不能對(duì)未存在的數(shù)據(jù)進(jìn)行加鎖操作,所以會(huì)存在幻讀的問(wèn)題。
4、可串行化:通過(guò)加鎖,所有的操作都是單版本,串行化的。
3.3.5 一致性的實(shí)現(xiàn)
一致性可以歸納為完整性。而數(shù)據(jù)的完整性是通過(guò)上面三個(gè)特性來(lái)保證的,包括原子性、隔離性、持久性,而這三個(gè)特性又是通過(guò)Redo/Undo/binlog 來(lái)保證的。
4 總結(jié)
本文根據(jù)我自己的學(xué)習(xí)過(guò)程,簡(jiǎn)單整理了事務(wù),事務(wù)特性,以及事務(wù)特性背后的技術(shù)原理。
5 參考文獻(xiàn)
淺入淺出mysql https://draveness.me/mysql-innodb
淺入深出MySQL中事務(wù)的實(shí)現(xiàn) https://draveness.me/mysql-transaction
詳細(xì)分析MySQL事務(wù)日志(redo log和undo log) https://www.cnblogs.com/f-ck-need-u/archive/2018/05/08/9010872.html#auto_id_16
拉勾網(wǎng)的《高性能mysql實(shí)戰(zhàn)》課程 https://kaiwu.lagou.com/course/courseInfo.htm?courseId=5#/content?courseId=5
6 其他
本文是mysql學(xué)習(xí)的第一篇-事務(wù)及其特性,希望對(duì)你有所幫助~
如果有疑問(wèn),可以直接留言,也可以關(guān)注公眾號(hào) “鏈人成長(zhǎng)chainerup” 提問(wèn)留言,或者加入知識(shí)星球“鏈人成長(zhǎng)” 與我深度鏈接~