詳解MySQL如何保證數(shù)據(jù)一致性
MySQL保證的一致性
在這之前先劃清一下界限,看一下MySQL保證的是哪里的一致性。
拿一個(gè)最簡(jiǎn)單的轉(zhuǎn)賬例子,用戶A向用戶B轉(zhuǎn)1000元,正常的sql是這樣的
update account set balance=balance-4000 where user='A' and balance >= 4000; update account set balance=balance+4000 where user='B';
示例表數(shù)據(jù)如下
如果最終用戶A賬戶沒(méi)有扣4000,而用戶B賬戶多了4000,總金額也無(wú)緣無(wú)故的多了4000。這個(gè)時(shí)候就造成了數(shù)據(jù)不一致了。出現(xiàn)這個(gè)問(wèn)題可能存在幾個(gè)原因:
- 在MySQL客戶端執(zhí)行sql時(shí)沒(méi)有做校驗(yàn)。如果用戶A余額并不足以4000,由于沒(méi)有校驗(yàn),兩條sql都會(huì)成功執(zhí)行,用戶B就會(huì)憑空多出4000。
- 兩條sql并不在同一事務(wù)中??赡躶ql1執(zhí)行失敗了,sql2執(zhí)行成功,由于不再一個(gè)事務(wù)中導(dǎo)致用戶B多了4000。
- 在MySQL內(nèi)部執(zhí)行時(shí)因?yàn)槟承┕收隙霈F(xiàn)了不一致情況。
很顯然,第三點(diǎn)是需要MySQL解決處理的。而第一點(diǎn)是屬于MySQL客戶端的邏輯BUG,第二點(diǎn)會(huì)存在客戶端在使用事務(wù)時(shí)不遵循規(guī)則的情況,都屬于外部因素,MySQL不可控。
所以,MySQL保證的一致性是:在一個(gè)事務(wù)中的DML(增刪改)操作。盡管DML本身可能存在問(wèn)題。
MySQL發(fā)生不一致環(huán)節(jié)
劃清界限后再分析一下在DML執(zhí)行過(guò)程中,哪個(gè)環(huán)節(jié)會(huì)發(fā)生數(shù)據(jù)不一致。 以上面的sql為例,假設(shè)已經(jīng)進(jìn)行過(guò)校驗(yàn)且在同一事務(wù)。
并發(fā)沖突
在執(zhí)行第一條sql時(shí),「執(zhí)行器」會(huì)通過(guò)條件user='A' and balance >= 4000
在「存儲(chǔ)引擎」獲取到符合條件的記錄,然后進(jìn)行balance扣減操作。(不知道這個(gè)流程的可以看下前面的文章)
如果這個(gè)時(shí)候存在并發(fā)現(xiàn)象,扣減操作可能會(huì)執(zhí)行多次,這個(gè)balance肯定就不是預(yù)想中的結(jié)果了,也就發(fā)生數(shù)據(jù)不一致了。如下圖
當(dāng)3個(gè)update請(qǐng)求同一時(shí)間調(diào)用存儲(chǔ)引擎對(duì)同一數(shù)據(jù)頁(yè)更新后,正常情況下,balance值應(yīng)該為0。但是因?yàn)椴l(fā)操作,balance的值可能會(huì)被修改為-1000或者-2000等其他值,這樣的bug顯然是不可被接受的。
有并發(fā)經(jīng)驗(yàn)的應(yīng)該都知道需要通過(guò)鎖資源可以避免這個(gè)情況,InnoDB也是通過(guò)加鎖來(lái)處理的。
redolog不完整
通過(guò)上文可以知道,InnoDB是通過(guò)「雙寫(xiě)緩沖」、「Redo Log」等機(jī)制保證數(shù)據(jù)不丟失的。
這種情況下,假設(shè)第一條sql執(zhí)行成功并且對(duì)應(yīng)的redo log已經(jīng)被刷新到磁盤(pán)中,但是第二條sql執(zhí)行失敗或者M(jìn)ySQL服務(wù)宕機(jī)導(dǎo)致其redolog未刷新到磁盤(pán),那么在下次啟動(dòng)恢復(fù)時(shí),就會(huì)發(fā)生數(shù)據(jù)不一致了。如下圖
sql示例的第一條執(zhí)行結(jié)果通過(guò)redolog恢復(fù)了,但是第二條的redolog隨著宕機(jī)丟失了,于是乎造成了數(shù)據(jù)的不一致。(redo log的刷盤(pán)機(jī)制和構(gòu)建臟頁(yè)可以通過(guò)上文進(jìn)行了解。)
對(duì)于這種情況,InnoDB是通過(guò)上文提到的「Undo Log」來(lái)解決的。
binlog&redolog不一致
我們知道,binlog中記錄了所有對(duì)數(shù)據(jù)更新的原始sql,以便數(shù)據(jù)備份恢復(fù)、主從復(fù)制。與redolog不一樣的是binlog屬于MySQL server層,而redolog是InnoDB的機(jī)制,用于故障恢復(fù),兩者并不沖突,這里不過(guò)多贅述。
雖然不沖突,但是要保證兩者在事務(wù)提交后都可以持久化到磁盤(pán),不然就會(huì)在主從復(fù)制的時(shí)候出現(xiàn)數(shù)據(jù)不一致現(xiàn)象,如下圖
只要binlog和redolog有一方?jīng)]有同步持久到磁盤(pán)都會(huì)發(fā)生類似現(xiàn)象。針對(duì)這種情況MySQL是通過(guò)兩階段提交解決的。
以上就是DML在執(zhí)行過(guò)程中可能出現(xiàn)不一致的環(huán)節(jié)(沒(méi)有想到的歡迎評(píng)論交流)。接下來(lái)具體看一下InnoDB針對(duì)以上幾種情況是如何處理解決,從而保證數(shù)據(jù)一致性的。
MySQL解決不一致方案
加鎖解決并發(fā)沖突
鎖沒(méi)有什么好說(shuō)的,innoDB根據(jù)隔離級(jí)別決定是否用鎖(當(dāng)然,還有server層的表鎖什么的這里不展開(kāi))。這里就演示下在隔離級(jí)別REPEATABLE-READ
下,鎖在SQL執(zhí)行中的具體作用和效果。
當(dāng)在第一個(gè)事務(wù)中執(zhí)行 update account set balance=balance-4000 where user='A' and balance >= 4000;
時(shí),其他事務(wù)不能對(duì)user為’A’的記錄進(jìn)行更新。如下圖,當(dāng)?shù)诙€(gè)事務(wù)窗口執(zhí)行 update account set balance=balance-1000 where user='A' and balance >= 1000;
時(shí)會(huì)被阻塞住,直到第一個(gè)事務(wù)提交或者超時(shí)。
這個(gè)時(shí)候可以通過(guò) select * from sys.innodb_lock_waits ;
查看一下鎖的相關(guān)信息
這里的locked_type
是RECORD,也就是行記錄鎖,還有一個(gè)是間隙鎖。
間隙鎖的作用是保證某個(gè)范圍內(nèi)的數(shù)據(jù)在鎖定情況下不會(huì)發(fā)生任何變化。比如,當(dāng)?shù)谝粋€(gè)事務(wù)執(zhí)行update account set balance=balance-100 where id between 7 and 9;
后,第二個(gè)事務(wù)在執(zhí)行INSERT INTO account (id, user,balance) VALUES (8, 'ABD',5000);
時(shí)會(huì)阻塞,但是執(zhí)行 INSERT INTO account (id, user,balance) VALUES (16, 'ABDD',5000);
會(huì)成功執(zhí)行,因?yàn)椴迦雐d為16的行數(shù)據(jù)不會(huì)影響到7~9之間的數(shù)據(jù)。這個(gè)時(shí)候去查看select * from sys.innodb_lock_waits;
時(shí)會(huì)發(fā)現(xiàn)waiting_lock_mode
值為 X,GAP(間隙)
。
所以說(shuō),鎖避免了事務(wù)的并發(fā)訪問(wèn)導(dǎo)致的數(shù)據(jù)不一致。
undolog解決redolog不完整
InnoDB在因sql執(zhí)行失敗或者M(jìn)ySQL服務(wù)宕機(jī)導(dǎo)致redolog不完整從而出現(xiàn)數(shù)據(jù)不一致是這么解決的:
- 在更新數(shù)據(jù)頁(yè)之前,InnoDB會(huì)先將數(shù)據(jù)當(dāng)前的狀態(tài)記錄在「Undo Log」中。
- 之后,再將更新后的相關(guān)數(shù)據(jù)記錄到「Redo Log」中。
這樣的話,不論出現(xiàn)哪種情況都可以通過(guò)undo log將數(shù)據(jù)回滾并保持一致,這個(gè)就是經(jīng)常提到的原子性以及「回滾」操作。
就如上圖(redo log不完整環(huán)節(jié)),加上Undo log之后數(shù)據(jù)狀態(tài)如下圖
圖中加了行記錄的隱藏字段事務(wù)ID和回滾指針以及undo log頁(yè)和undo的redo。
undo log 記錄的就是user='A’和‘B’事務(wù)提交前的數(shù)據(jù),各為4000。
redo log 中會(huì)記錄所有的更新操作,包括undo,因?yàn)閡ndo記錄的也是更新語(yǔ)句。需要說(shuō)一下,這里記錄的undo是演示使用,對(duì)于一條update操作,真正的undo會(huì)記錄一條delete和一條insert操作,原因上文有介紹。
為什么redo log會(huì)記錄undo
undo log是以頁(yè)為單位,跟隨頁(yè)的刷新機(jī)制,會(huì)存在丟失的情況,所以在記錄undo后也會(huì)將該undo記錄到redo,避免undo丟失,一旦undo丟失就回滾不了了。
有了undo log后,假設(shè)第二條sql執(zhí)行失敗,這個(gè)時(shí)候就會(huì)通過(guò)行記錄中的事務(wù)ID(txidx)和回滾指針(roll_pointx、roll_pointx1)去undolog中找對(duì)應(yīng)的回滾操作(如圖中的 ‘**回滾指針’**箭頭),最終將事務(wù)回滾保證原子性和一致性。
針對(duì)上圖的狀態(tài),如果發(fā)生宕機(jī),那么在重新MySQL服務(wù)時(shí),會(huì)有兩個(gè)操作:
- 會(huì)先通過(guò)redo log構(gòu)建「臟頁(yè)」。
- 根據(jù)redo log中記錄的事務(wù)提交狀態(tài)來(lái)決定是否回滾。
如圖
當(dāng)前user='A’的事務(wù)狀態(tài)為prepare,所以需要進(jìn)行回滾操作。回滾流程是這樣的:
- 根據(jù)數(shù)據(jù)中該記錄的事務(wù)ID(txidx)在undolog中找對(duì)應(yīng)的回滾操作。
- 發(fā)現(xiàn)事務(wù)ID有兩個(gè)undo操作,user='A’和‘B’的。
- 執(zhí)行undo操作,將數(shù)據(jù)頁(yè)中的記錄回滾至事務(wù)提交前狀態(tài)。
最終的結(jié)果就是user='A’和‘B’的balance會(huì)回滾到4000。
所以說(shuō),undo避免了事務(wù)或者宕機(jī)的異常導(dǎo)致的數(shù)據(jù)不一致。
XA兩階段提交解決binlog和redolog的不一致
redo log中的事務(wù)狀態(tài)不僅在這里起到作用,在binlog和redolog的一致上,同樣是通過(guò)這個(gè)狀態(tài)來(lái)判斷并且決定是否需要回滾。
這個(gè)就不得不說(shuō)到MySQL的XA兩階段提交協(xié)議了,在這之前,我一直以為XA是運(yùn)用到MySQL與外部應(yīng)用的,沒(méi)想到是應(yīng)用在MySQL內(nèi)部的。不過(guò)分布式事務(wù)嘛,原理基本上都一樣。
XA的兩階段分別是prepare和commit,在事務(wù)提交前,redolog中記錄的狀態(tài)都是prepare,當(dāng)事務(wù)提交后,該狀態(tài)就會(huì)被更新為commit,同時(shí)將XID寫(xiě)入到對(duì)應(yīng)的binlog中并刷新到磁盤(pán)。如下圖
這樣的話,如果發(fā)生宕機(jī),下次啟動(dòng)時(shí)可以根據(jù)redolog中的狀態(tài)以及XID去binlog中查找,如果存在意味著兩者一致,不存在就進(jìn)行回滾操作。
所以說(shuō),XA兩階段提交保證了binlog和redolog邏輯一致,從而避免主從節(jié)點(diǎn)的數(shù)據(jù)不一致。
總結(jié)
MySQL一致性的保證基本上涉及到InnoDB存儲(chǔ)引擎的各個(gè)組件,「Buffer Pool」、「Log Buffer」、「Redo Log」、「Undo Log」等,還有DML操作的流程、鎖、故障恢復(fù)等功能。最后再總結(jié)下MySQL是如何保證一致性的。
- 對(duì)于并發(fā)操作帶來(lái)的數(shù)據(jù)不一致性問(wèn)題,InnoDB通過(guò)鎖來(lái)解決。
- 對(duì)于可能會(huì)發(fā)生的redolog不完整的情況,InnoDB通過(guò)Undo Log來(lái)解決。
- 對(duì)于redolog&binlog不一致帶來(lái)的主從節(jié)點(diǎn)數(shù)據(jù)不一致,MySQL是通過(guò)XA兩階段提交來(lái)解決。
以上就是詳解MySQL如何保證數(shù)據(jù)一致性的詳細(xì)內(nèi)容,更多關(guān)于MySQL保證數(shù)據(jù)一致性的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
MySQL主從復(fù)制原理解析與最佳實(shí)踐過(guò)程
這篇文章主要介紹了MySQL主從復(fù)制原理解析與最佳實(shí)踐過(guò)程,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-05-05MySQL表數(shù)據(jù)文件損壞導(dǎo)致數(shù)據(jù)庫(kù)無(wú)法啟動(dòng)的原因與解決方案
在日常的數(shù)據(jù)庫(kù)管理中,遇到MySQL表數(shù)據(jù)文件損壞的情況并不罕見(jiàn),這種情況下,MySQL數(shù)據(jù)庫(kù)可能會(huì)無(wú)法正常啟動(dòng),給業(yè)務(wù)運(yùn)行帶來(lái)嚴(yán)重影響,本文將探討如何診斷和解決MySQL表數(shù)據(jù)文件損壞導(dǎo)致的數(shù)據(jù)庫(kù)無(wú)法啟動(dòng)問(wèn)題,需要的朋友可以參考下2025-03-03MySQL?Workbench菜單漢化為中文的簡(jiǎn)單操作方法
這篇文章主要給大家介紹了關(guān)于MySQL?Workbench菜單漢化為中文的簡(jiǎn)單操作方法,文中通過(guò)圖文及實(shí)例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用MySQL?Workbench具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2024-11-11面試被問(wèn)select......for update會(huì)鎖表還是鎖行
select … for update 是我們常用的對(duì)行加鎖的一種方式,那么select......for update會(huì)鎖表還是鎖行,本文就詳細(xì)的來(lái)介紹一下,感興趣的可以了解一下2021-11-11MySQL5.6 Replication主從復(fù)制(讀寫(xiě)分離) 配置完整版
這篇文章主要介紹了MySQL5.6 Replication主從復(fù)制(讀寫(xiě)分離) 配置完整版,需要的朋友可以參考下2016-04-04MySQL使用觸發(fā)器如何實(shí)現(xiàn)CHECK檢查約束的功能
這篇文章主要介紹了MySQL使用觸發(fā)器如何實(shí)現(xiàn)CHECK檢查約束的功能,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-01-01