mysql事務(wù)和隔離級(jí)別底層原理淺析
前言
首先回顧一下什么是事務(wù),事務(wù)是數(shù)據(jù)庫(kù)操作的最小工作單元,是作為單個(gè)邏輯工作單元執(zhí)行的一系列操作;這些操作作為一個(gè)整體一起向系統(tǒng)提交,要么都執(zhí)行、要么都不執(zhí)行;事務(wù)是一組不可再分割的操作集合(工作邏輯單元)。
事務(wù)的特性:
原子性(Atomicity):原子性是指事務(wù)包含的所有操作要么全部成功,要么全部失敗回滾。一致性(Consistency):事務(wù)執(zhí)行的結(jié)果必須是使數(shù)據(jù)庫(kù)從一個(gè)一致性狀態(tài)變到另一個(gè)一致性狀態(tài)。隔離性(Isolation):一個(gè)事務(wù)的執(zhí)行不能其它事務(wù)干擾。即一個(gè)事務(wù)內(nèi)部的操作及使用的數(shù)據(jù)對(duì)其它并發(fā)事務(wù)是隔離的,并發(fā)執(zhí)行的各個(gè)事務(wù)之間不能互相干擾。持久性(Durability):指一個(gè)事務(wù)一旦提交,它對(duì)數(shù)據(jù)庫(kù)中的數(shù)據(jù)的改變就應(yīng)該是永久性的。接下來的其它操作或故障不應(yīng)該對(duì)其執(zhí)行結(jié)果有任何影響。
一、事務(wù)底層原理淺析
原子性:
實(shí)現(xiàn)原理:undo log
undo log也被成為回滾日志,它是事務(wù)實(shí)現(xiàn)原子性和隔離性的基礎(chǔ)。當(dāng)事務(wù)對(duì)數(shù)據(jù)庫(kù)進(jìn)行修改時(shí),InnoDB會(huì)生成對(duì)應(yīng)的undo log;如果事務(wù)執(zhí)行失敗或調(diào)用了rollback,導(dǎo)致事務(wù)需要回滾,便可以利用undo log中的信息將數(shù)據(jù)回滾到修改之前的樣子。
undo log屬于邏輯日志,它記錄的是sql執(zhí)行相關(guān)的信息。當(dāng)發(fā)生回滾時(shí),InnoDB會(huì)根據(jù)undo log的內(nèi)容做與之前相反的工作:對(duì)于每個(gè)insert,回滾時(shí)會(huì)執(zhí)行delete;對(duì)于每個(gè)delete,回滾時(shí)會(huì)執(zhí)行insert;對(duì)于每個(gè)update,回滾時(shí)會(huì)執(zhí)行一個(gè)相反的update,把數(shù)據(jù)改回去。
undo 存放在數(shù)據(jù)庫(kù)內(nèi)部的一個(gè)特殊段segment中,這個(gè)段稱為undo段。undo段位于共享表空間中。undo是邏輯日志,因此只是將數(shù)據(jù)庫(kù)邏輯的恢復(fù)到原來的樣子。undo log會(huì)產(chǎn)生redo log,也就是undo log的產(chǎn)生會(huì)伴隨著redo log的產(chǎn)生,因?yàn)閡ndo log也需要持久性的保護(hù)。
undo log執(zhí)行記錄是在每次寫入數(shù)據(jù)或者修改數(shù)據(jù)之前。
undo log使用原理:數(shù)據(jù)庫(kù)表每行數(shù)據(jù)會(huì)多兩列DATA_TRX_ID和DATA_ROLL_PTR(可能還有一列DB_ROW_ID,當(dāng)沒有默認(rèn)主鍵時(shí)會(huì)自動(dòng)加上這列)。DATA_TRX_ID表示當(dāng)前數(shù)據(jù)的事務(wù)版本, DATA_ROLL_PTR 則指向剛剛拷貝到
undo log 鏈中的舊版本記錄,undo log是個(gè)鏈表,如果多個(gè)事務(wù)多次修改會(huì)繼續(xù)生成undo log并通過DATA_ROLL_PTR建立指向關(guān)系。用圖說明一下:
這樣,一旦事務(wù)發(fā)生回滾,mysql便可以借助undo log實(shí)現(xiàn)數(shù)據(jù)還原,從而保證未提交事務(wù)的原子性。
持久性
實(shí)現(xiàn)原理:redo log
由于InnoDB作為MySQL的存儲(chǔ)引擎,數(shù)據(jù)是存放在磁盤中的,為了減少磁盤IO,提高讀取性能InnoDB提供了緩存池——Buffer Pool。Buffer Pool中包含了磁盤中部分?jǐn)?shù)據(jù)頁(yè)的映射,作為訪問數(shù)據(jù)庫(kù)的緩沖:當(dāng)從數(shù)據(jù)庫(kù)讀取數(shù)據(jù)時(shí),會(huì)首先從Buffer Pool中讀取,如果Buffer Pool中沒有,則從磁盤讀取后放入Buffer Pool;當(dāng)向數(shù)據(jù)庫(kù)寫入數(shù)據(jù)時(shí),會(huì)首先寫入Buffer Pool,Buffer Pool中修改的數(shù)據(jù)會(huì)定期刷新到磁盤中(這一過程稱為刷臟)。
不過這也帶來了一個(gè)新的問題,如果MySQL宕機(jī),而此時(shí)Buffer Pool中修改的數(shù)據(jù)還沒有刷新到磁盤,就會(huì)導(dǎo)致數(shù)據(jù)的丟失,事務(wù)的持久性無法保證。
為了解決這個(gè)問題就引入了redo log,也叫重做日志。當(dāng)數(shù)據(jù)修改時(shí),除了修改Buffer Pool中的數(shù)據(jù),還會(huì)在redo log記錄這次操作;當(dāng)事務(wù)提交時(shí),會(huì)調(diào)用fsync接口對(duì)redo log進(jìn)行刷盤。如果MySQL宕機(jī),重啟時(shí)可以讀取redo log中的數(shù)據(jù),對(duì)數(shù)據(jù)庫(kù)進(jìn)行恢復(fù)。redo log采用的是WAL(Write-ahead logging,預(yù)寫式日志),所有修改在提交前先寫入日志,保證了數(shù)據(jù)不會(huì)因MySQL宕機(jī)而丟失,從而滿足了持久性要求。redo log是針對(duì)物理頁(yè)的,并發(fā)執(zhí)行,最后一次提交會(huì)覆蓋未提交的數(shù)據(jù)。本地redo log:
redo log也是有緩沖區(qū)的——redo log buffer,當(dāng)事務(wù)提交之后會(huì)把所有修改信息都刷新到磁盤上。用戶也可以通過控制通過變量 innodb_flush_log_at_trx_commit 的值來修改刷新策略(默認(rèn)1),如設(shè)置值為2,控制成每秒刷新,這樣事務(wù)提
交就會(huì)較快,不過可能面臨日志丟失的風(fēng)險(xiǎn)。
redo log是在SQL語(yǔ)句執(zhí)行之后記錄的。
既然redo log也需要存儲(chǔ),也涉及磁盤IO為啥還用它?
(1)redo log 的存儲(chǔ)是順序存儲(chǔ),而緩存同步是隨機(jī)操作。
(2)緩存同步是以數(shù)據(jù)頁(yè)為單位的,每次傳輸?shù)臄?shù)據(jù)大小大于redo log。
redo log是用來恢復(fù)數(shù)據(jù)的 用于保障已提交事務(wù)的持久化特性。
隔離性:
原理:
(1). 寫操作對(duì)寫操作的影響:鎖機(jī)制保證隔離性
(2). 寫操作對(duì)讀操作的影響:MVCC保證隔離性
一致性:
一致性比較特殊,前面的原子性、持久性和隔離性都是為一致性服務(wù)的,除此之外,一致性還依賴于數(shù)據(jù)庫(kù)自我提供的保障,如SQL語(yǔ)法驗(yàn)證,列類型插入數(shù)據(jù)類型驗(yàn)證,同時(shí)也依賴于應(yīng)用層的保障,如轉(zhuǎn)賬操作,需要開發(fā)人員進(jìn)行轉(zhuǎn)賬者的余額扣除和接受者的余額增加,如果應(yīng)用層面出現(xiàn)問題,那么一致性也是無法保障的。
二、隔離級(jí)別底層原理淺析
在事務(wù)底層原理淺析中,關(guān)于隔離性的原理沒有過多深入,在此我們簡(jiǎn)單介紹一下。
首先介紹一下mysql的MVCC(MultiVersion Concurrency Control) 叫做多版本并發(fā)控制。它是依賴undo log與read view實(shí)現(xiàn)的。undo log上文已經(jīng)介紹過了,不再贅述,read view(可讀視圖),這與我們平時(shí)理解的數(shù)據(jù)庫(kù)視圖不同,它是用來判斷當(dāng)前數(shù)據(jù)版本的可見性的。
readview主要有四個(gè)屬性:
(1). m_ids 代表生成ReadView時(shí),當(dāng)前所有活躍的事務(wù)ID,活躍的意思就是事務(wù)開啟了還沒提交;
(2). min_trx_id 表示當(dāng)前活躍的mIds中最小的事務(wù)ID;
(3). max_trx_id 表示生成ReadView時(shí),最大的事務(wù)ID,它不一定是mIds中最大的事務(wù)ID;
(4).creator_trx_id表示創(chuàng)建該ReadView的事務(wù)ID。
注意:每開啟一個(gè)事務(wù),事務(wù)ID就自增一次,事務(wù)ID可以看做一個(gè)全局自增變量。最先開啟的事務(wù)不一定比后開啟的事務(wù)先提交,比如長(zhǎng)連接,所以不要認(rèn)為max_trx_id就是mIds中的最大值。
read view是怎樣借助上面四個(gè)屬性,判斷事務(wù)應(yīng)該讀取那個(gè)版本的數(shù)據(jù)呢?
如果被訪問版本的 data_trx_id 小于 m_ids 中的最小值,說明生成該版本的事務(wù)在 ReadView 生成前就已經(jīng)提交了,那么該版本可以被當(dāng)前事務(wù)訪問。
如果被訪問版本的 data_trx_id 屬性值與ReadView中的creator_trx_id值相同,意味著當(dāng)前事務(wù)在訪問它自己修改過的記錄,所以該版本可以被當(dāng)前事務(wù)訪問。
如果被訪問版本的 data_trx_id 屬性值大于ReadView中的max_trx_id值,表明生成該版本的事務(wù)在當(dāng)前事務(wù)生成ReadView后才開啟,所以該版本不可以被當(dāng)前事務(wù)訪問。
如果被訪問版本的 data_trx_id 屬性值在ReadView的min_trx_id和max_trx_id之間,那就需要判斷一下trx_id屬性值是不是在m_ids列表中,如果在,說明創(chuàng)建ReadView時(shí)生成該版本的事務(wù)還是活躍的,該版本不可以被訪問;如果不在,說明創(chuàng)建ReadView時(shí)生成該版本的事務(wù)已經(jīng)被提交,該版本可以被訪問。
當(dāng)一個(gè)事務(wù)要讀取一行數(shù)據(jù),首先用上面規(guī)則判斷數(shù)據(jù)的最新版本也就是那行記錄,如果發(fā)現(xiàn)可以訪問就直接讀取了,如果發(fā)現(xiàn)不能訪問,就通過DATA_ROLL_PTR指針找到undo log,遞歸往下去找每個(gè)版本,知道讀取到自己可以讀取的版本為止,如果讀取不到就返回空。
所以訪問數(shù)據(jù)時(shí),數(shù)據(jù)庫(kù)里面會(huì)創(chuàng)建一個(gè)視圖,訪問的時(shí)候以視圖的邏輯結(jié)果為準(zhǔn):
READ UNCOMMITED (未提交讀):此隔離級(jí)別下直接返回記錄上的最新值,沒有視圖概念。因?yàn)樽x不會(huì)加任何鎖,所以寫操作在讀的過程中修改數(shù)據(jù),所以會(huì)造成臟讀。好處是可以提升并發(fā)處理性能,能做到讀寫并行。
READ COMMITED (提交讀):此隔離級(jí)別下,這個(gè)視圖是在每個(gè) SQL語(yǔ)句開始執(zhí)行的時(shí)候創(chuàng)建的。InnoDB在 READ COMMITTED,使用排它鎖,讀取數(shù)據(jù)不加鎖而是使用了MVCC機(jī)制?;蛘邠Q句話說他采用了讀寫分離機(jī)制。
REPEATABLE READ (可重復(fù)讀):此隔離級(jí)別下,這個(gè)視圖是在事務(wù)啟動(dòng)時(shí)創(chuàng)建的,整個(gè)事務(wù)存在期間都用這個(gè)視圖。
SERIALIZABLE (串行化):此隔離級(jí)別下直接用加鎖的方式來避免并行訪問。
寫到這里,你也許發(fā)現(xiàn)了,MySQL中借助MVCC在可重復(fù)讀隔離級(jí)別下其實(shí)也杜絕了幻讀的發(fā)生。
三、總結(jié)
原子性:使用 undo log (回滾日志)實(shí)現(xiàn)回滾,從而保障未提交事務(wù)的原子性;
持久性:使用 redo log(重做日志)實(shí)現(xiàn)數(shù)據(jù)恢復(fù),從而保障已提交事務(wù)的持久性;
隔離性:使用鎖以及MVCC思想實(shí)現(xiàn)讀寫分離,讀讀并行,讀寫并行;
一致性:通過回滾、恢復(fù)和在并發(fā)環(huán)境下的隔離做到一致性。
到此這篇關(guān)于mysql事務(wù)和隔離級(jí)別底層原理淺析的文章就介紹到這了,更多相關(guān)mysql事務(wù)和隔離級(jí)別內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Mybatis mapper動(dòng)態(tài)代理的原理解析
這篇文章主要介紹了Mybatis mapper動(dòng)態(tài)代理的原理解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-08-08DataGrip連接Mysql并創(chuàng)建數(shù)據(jù)庫(kù)的方法實(shí)現(xiàn)
本文主要介紹了DataGrip連接Mysql并創(chuàng)建數(shù)據(jù)庫(kù)的方法實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-02-02MySQL實(shí)現(xiàn)顯示百分比顯示和前百分之幾的方法
這篇文章主要介紹了MySQL中如何顯示百分比和顯示前百分之幾的,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)MySQL有一定的幫助,感興趣的小伙伴可以了解一下2021-12-12MySQL查看event執(zhí)行記錄的實(shí)現(xiàn)
在使用EVENT的過程中,我們可能會(huì)需要查看EVENT的執(zhí)行記錄,以便了解它們是否按預(yù)期執(zhí)行,本文就來介紹一下MySQL查看event執(zhí)行記錄的實(shí)現(xiàn),感興趣的可以了解一下2023-11-11mysql查詢優(yōu)化之100萬條數(shù)據(jù)的一張表優(yōu)化方案
這篇文章主要介紹了mysql查詢優(yōu)化之100萬條數(shù)據(jù)的一張表優(yōu)化方案,需要的朋友可以參考下2021-05-05