MySQL持久化和回滾該怎么理解

這篇文章跟大家分析一下“MySQL持久化和回滾該怎么理解”。內容詳細易懂,對“MySQL持久化和回滾該怎么理解”感興趣的朋友可以跟著小編的思路慢慢深入來閱讀一下,希望閱讀后能夠對大家有所幫助。下面跟著小編一起深入學習“MySQL持久化和回滾該怎么理解”的知識吧。

成都創(chuàng)新互聯(lián)堅持“要么做到,要么別承諾”的工作理念,服務領域包括:網(wǎng)站制作、網(wǎng)站設計、企業(yè)官網(wǎng)、英文網(wǎng)站、手機端網(wǎng)站、網(wǎng)站推廣等服務,滿足客戶于互聯(lián)網(wǎng)時代的信宜網(wǎng)站設計、移動媒體設計的需求,幫助企業(yè)找到有效的互聯(lián)網(wǎng)解決方案。努力成為您成熟可靠的網(wǎng)絡建設合作伙伴!

redo log

事務的支持是數(shù)據(jù)庫區(qū)分文件系統(tǒng)的重要特征之一,事務的四大特性:

  • 原子性:所有的操作要么都做,要么都不做,不可分割。

  • 一致性:數(shù)據(jù)庫從一種狀態(tài)變成另一種狀態(tài)的的結果最終是一致的,比如A給B轉賬500,A最終少了500,B最終多了500,但是A+B的值始終沒變。

  • 隔離性:事務和事務之前相互隔離,互不干擾。

  • 持久性:事務一旦提交,它對數(shù)據(jù)的變更是永久性的。

本篇文章主要說說持久性相關的知識。

當我們在事務中更新一條記錄的時候,比如:

update user set age=11 where user_id=1;

它的流程大概是這樣的:

  1. 先判斷user_id這條數(shù)據(jù)所在的頁是否在內存里,如果不在的話,先從數(shù)據(jù)庫讀取到,然后加載到內存中

  2. 修改內存中的age為11

  3. 寫入redo log,并且redo log處于prepare狀態(tài)

  4. 寫入binlog

  5. 提交事務,redo log變成commit狀態(tài)

MySQL持久化和回滾該怎么理解

這里面有幾個關鍵的點:redo log是什么?為什么需要redo log?prepare狀態(tài)的redo log是什么?redo log和binlog是否可以只選其一...?帶著這一系列的問題,我們來揭開redo log的面紗。

為什么要先更新內存數(shù)據(jù),不直接更新磁盤數(shù)據(jù)?

我們?yōu)槭裁床幻看胃聰?shù)據(jù)的時候,直接更新對應的磁盤數(shù)據(jù)?首先我們知道磁盤IO是緩慢的,內存是快速的,兩者的速度不是一個量級的,那么針對緩慢的磁盤IO,出現(xiàn)了索引,通過索引哪怕數(shù)據(jù)成百上千萬我們依然可以在磁盤上很快速的找我們的數(shù)據(jù),這就是索引的作用。但是索引也需要維護,并不是一成不變的,當我們插入一條新數(shù)據(jù)A的時候,由于這條數(shù)據(jù)要插入在已存在的數(shù)據(jù)B之后,那么就要移動B數(shù)據(jù),讓出一個位置給A,這個有一定的開銷。

更糟糕的是,本來要插入的頁已經(jīng)滿了,那么就要申請一個新的頁,然后挪一部分數(shù)據(jù)過去,這叫做頁的分裂,這個開銷更大。如果我們的sql變更是直接修改磁盤的數(shù)據(jù),恰巧正好出現(xiàn)上面的問題,那么此時的效率就會很低,嚴重的話會造成超時,這也是上面更新的過程為什么先要加載對應的數(shù)據(jù)頁到內存中,然后先更新內存中的數(shù)據(jù)的原因。對于mysql來說,所有的變更都必須先更新緩沖池中的數(shù)據(jù),然后緩沖池中的臟頁會以一定的頻率被刷入磁盤(checkPoint機制),通過緩沖池來優(yōu)化CPU和磁盤之間的鴻溝,這樣就可以保證整體的性能不會下降太快。

為什么需要redo log?

緩沖池可以幫助我們消除CPU和磁盤之間的鴻溝,checkpoint機制可以保證數(shù)據(jù)的最終落盤,然而由于checkpoint并不是每次變更的時候就觸發(fā)的,而是master線程隔一段時間去處理的。所以最壞的情況就是剛寫完緩沖池,數(shù)據(jù)庫宕機了,那么這段數(shù)據(jù)就是丟失的,無法恢復。這樣的話就不滿足ACID中的D,為了解決這種情況下的持久化問題,InnoDB引擎的事務采用了WAL技術(Write-Ahead Logging),這種技術的思想就是先寫日志,再寫磁盤,只有日志寫入成功,才算事務提交成功,這里的日志就是redo log。當發(fā)生宕機且數(shù)據(jù)未刷到磁盤的時候,可以通過redo log來恢復,保證ACID中的D,這就是redo log的作用。

redo log是如何實現(xiàn)的?

redo log的寫入并不是直接寫入磁盤的,redo log也有緩沖區(qū)的,叫做redo log buffer(重做日志緩沖),InnoDB引擎會在寫redo log的時候先寫redo log buffer,然后也是以一定的頻率刷入到真正的redo log中,redo log buffer一般不需要特別大,它只是一個臨時的容器,master線程會每秒將redo log buffer刷到redo log文件中,因此我們只要保證redo log buffer能夠存下1s內的事務變更的數(shù)據(jù)量即可,以mysql5.7.23為例,這個默認是16M。

mysql> show variables like '%innodb_log_buffer_size%';
+------------------------+----------+
| Variable_name          | Value    |
+------------------------+----------+
| innodb_log_buffer_size | 16777216 |
+------------------------+----------+

16M的buffer足夠應對大部分應用了,buffer同步到redo log的策略主要有如下幾個:

  • master線程每秒將buffer刷到到redo log中

  • 每個事務提交的時候會將buffer刷到redo log中

  • 當buffer剩余空間小于1/2時,會被刷到redo log中

需要注意的是redo log buffer刷到redo log的過程并不是真正的刷到磁盤中去了,只是刷入到os cache中去,這是現(xiàn)代操作系統(tǒng)為了提高文件寫入的效率做的一個優(yōu)化,真正的寫入會交給系統(tǒng)自己來決定(比如os cache足夠大了)。那么對于InnoDB來說就存在一個問題,如果交給系統(tǒng)來fsync,同樣如果系統(tǒng)宕機,那么數(shù)據(jù)也丟失了(雖然整個系統(tǒng)宕機的概率還是比較小的)。針對這種情況,InnoDB給出innodb_flush_log_at_trx_commit策略,讓用戶自己決定使用哪個。

mysql> show variables like 'innodb_flush_log_at_trx_commit';
+--------------------------------+-------+
| Variable_name                  | Value |
+--------------------------------+-------+
| innodb_flush_log_at_trx_commit | 1     |
+--------------------------------+-------+
  • 0:表示事務提交后,不進行fsync,而是由master每隔1s進行一次重做日志的fysnc

  • 1:默認值,每次事務提交的時候同步進行fsync

  • 2:寫入os cache后,交給操作系統(tǒng)自己決定什么時候fsync

從3種刷入策略來說:

2肯定是效率最高的,但是只要操作系統(tǒng)發(fā)生宕機,那么就會丟失os cache中的數(shù)據(jù),這種情況下無法滿足ACID中的D

0的話,是一種折中的做法,它的IO效率理論是高于1的,低于2的,它的數(shù)據(jù)安全性理論是要低于1的,高于2的,這種策略也有丟失數(shù)據(jù)的風險,也無法保證D。

1是默認值,可以保證D,數(shù)據(jù)絕對不會丟失,但是效率最差的。個人建議使用默認值,雖然操作系統(tǒng)宕機的概率理論小于數(shù)據(jù)庫宕機的概率,但是一般既然使用了事務,那么數(shù)據(jù)的安全應該是相對來說更重要些。

redo log是對頁的物理修改,第x頁的第x位置修改成xx,比如:

page(2,4),offset 64,value 2

在InnoDB引擎中,redo log都是以512字節(jié)為單位進行存儲的,每個存儲的單位我們稱之為redo log block(重做日志塊),若一個頁中存儲的日志量大于512字節(jié),那么就需要邏輯上切割成多個block進行存儲。

一個redo log block是由日志頭、日志體、日志尾組成。日志頭占用12字節(jié),日志尾占用8字節(jié),所以一個block真正能存儲的數(shù)據(jù)就是512-12-8=492字節(jié)。

MySQL持久化和回滾該怎么理解

多個redo log block組成了我們的redo log。

MySQL持久化和回滾該怎么理解

每個redo log默認大小為48M:

mysql> show variables like 'innodb_log_file_size';
+----------------------+----------+
| Variable_name        | Value    |
+----------------------+----------+
| innodb_log_file_size | 50331648 |
+----------------------+----------+

InnoDB默認2個redo log組成一個log組,真正工作的就是這個log組。

mysql> show variables like 'innodb_log_files_in_group';
+---------------------------+-------+
| Variable_name             | Value |
+---------------------------+-------+
| innodb_log_files_in_group | 2     |
+---------------------------+-------+
#ib_logfile0
#ib_logfile1

當ib_logfile0寫完之后,會寫ib_logfile1,當ib_logfile1寫完之后,會重新寫ib_logfile0...,就這樣一直不停的循環(huán)寫。

MySQL持久化和回滾該怎么理解

為什么一個block設計成512字節(jié)?

這個和磁盤的扇區(qū)有關,機械磁盤默認的扇區(qū)就是512字節(jié),如果你要寫入的數(shù)據(jù)大于512字節(jié),那么要寫入的扇區(qū)肯定不止一個,這時就要涉及到盤片的轉動,找到下一個扇區(qū),假設現(xiàn)在需要寫入兩個扇區(qū)A和B,如果扇區(qū)A寫入成功,而扇區(qū)B寫入失敗,那么就會出現(xiàn)非原子性的寫入,而如果每次只寫入和扇區(qū)的大小一樣的512字節(jié),那么每次的寫入都是原子性的。

為什么要兩段式提交?

從上文我們知道,事務的提交要先寫redo log(prepare),再寫binlog,最后再提交(commit)。這里為什么要有個prepare的動作?redo log直接commit狀態(tài)不行嗎?假設redo log直接提交,在寫binlog的時候,發(fā)生了crash,這時binlog就沒有對應的數(shù)據(jù),那么所有依靠binlog來恢復數(shù)據(jù)的slave,就沒有對應的數(shù)據(jù),導致主從不一致。

所以需要通過兩段式(2pc)提交來保證redo log和binlog的一致性是非常有必要的。具體的步驟是:處于prepare狀態(tài)的redo log,會記錄2PC的XID,binlog寫入后也會記錄2PC的XID,同時會在redo log上打上commit標識。

redo log和bin log是否可以只需要其中一個?

不可以。redo log本身大小是固定的,在寫滿之后,會重頭開始寫,會覆蓋老數(shù)據(jù),因為redo log無法保存所有數(shù)據(jù),所以在主從模式下,想要通過redo log來同步數(shù)據(jù)給從庫是行不通的。那么binlog是一定需要的,binlog是mysql的server層產(chǎn)生的,和存儲引擎無關,binglog又叫歸檔日志,當一個binlog file寫滿之后,會寫入到一個新的binlog file中。

所以我們是不是只需要binlog就行了?redo log可以不需要?當然也不行,redo log的作用是提供crash-safe的能力,首先對于一個數(shù)據(jù)的修改,是先修改緩沖池中的數(shù)據(jù)頁的,這時修改的數(shù)據(jù)并沒有真正的落盤,這主要是因為磁盤的離散讀寫能力效率低,真正落盤的工作交給master線程定期來處理,好處就是master可以一次性把多個修改一起寫入磁盤。

那么此時就有一個問題,當事務commit之后,數(shù)據(jù)在緩沖區(qū)的臟頁中,還沒來的及刷入磁盤,此時數(shù)據(jù)庫發(fā)生了崩潰,那么這條commit的數(shù)據(jù)即使在數(shù)據(jù)庫恢復后,也無法還原,并不能滿足ACID中的D,然后就有了redo log,從流程來看,一個事務的提交必須保證redo log的寫入成功,只有redo log寫入成功才算事務提交成功,redo log大部分情況是順序寫的磁盤,所以它的效率要高很多。當commit后發(fā)生crash的情況下,我們可以通過redo log來恢復數(shù)據(jù),這也是為什么需要redo log的原因。

但是事務的提交也需要binlog的寫入成功,那為什么不可以通過binlog來恢復未落盤的數(shù)據(jù)?這是因為binlog不知道哪些數(shù)據(jù)落盤了,所以不知道哪些數(shù)據(jù)需要恢復。對于redo log而言,在數(shù)據(jù)落盤后對應的redo log中的數(shù)據(jù)會被刪除,那么在數(shù)據(jù)庫重啟后,只要把redo log中剩下的數(shù)據(jù)都恢復就行了。

crash后是如何恢復的?

通過兩段式提交我們知道redo log和binlog在各個階段會被打上prepare或者commit的標識,同時還會記錄事務的XID,有了這些數(shù)據(jù),在數(shù)據(jù)庫重啟的時候,會先去redo log里檢查所有的事務,如果redo log的事務處于commit狀態(tài),那么說明在commit后發(fā)生了crash,此時直接把redo log的數(shù)據(jù)恢復就行了,如果redo log是prepare狀態(tài),那么說明commit之前發(fā)生了crash,此時binlog的狀態(tài)決定了當前事務的狀態(tài),如果binlog中有對應的XID,說明binlog已經(jīng)寫入成功,只是沒來的及提交,此時再次執(zhí)行commit就行了,如果binlog中找不到對應的XID,說明binlog沒寫入成功就crash了,那么此時應該執(zhí)行回滾。

undo log

redo log是事務持久性的保證,undo log是事務原子性的保證。在事務中更新數(shù)據(jù)的前置操作其實是要先寫入一個undo log中的,所以它的流程大致如下:

MySQL持久化和回滾該怎么理解

什么情況下會生成undo log?

undo log的作用就是mvcc(多版本控制)和回滾,我們這里主要說回滾,當我們在事務里insert、update、delete某些數(shù)據(jù)的時候,就會產(chǎn)生對應的undo log,當我們執(zhí)行回滾時,通過undo log就可以回到事務開始的樣子。需要注意的是回滾并不是修改的物理頁,而是邏輯的恢復到最初的樣子,比如一個數(shù)據(jù)A,在事務里被你修改成B,但是此時有另一個事務已經(jīng)把它修改成了C,如果回滾直接修改數(shù)據(jù)頁把數(shù)據(jù)改成A,那么C就被覆蓋了。

對于InnoDB引擎來說,每個行記錄除了記錄本身的數(shù)據(jù)之外,還有幾個隱藏的列:

  • DB_ROW_ID:如果沒有為表顯式的定義主鍵,并且表中也沒有定義唯一索引,那么InnoDB會自動為表添加一個row_id的隱藏列作為主鍵。

  • DB_TRX_ID:每個事務都會分配一個事務ID,當對某條記錄發(fā)生變更時,就會將這個事務的事務ID寫入trx_id中。

  • DB_ROLL_PTR:回滾指針,本質上就是指向 undo log 的指針。

MySQL持久化和回滾該怎么理解

當我們執(zhí)行INSERT時:

begin;
INSERT INTO user (name) VALUES ("tom")

插入的數(shù)據(jù)都會生一條insert undo log,并且數(shù)據(jù)的回滾指針會指向它。undo log會記錄undo log的序號、插入主鍵的列和值...,那么在進行rollback的時候,通過主鍵直接把對應的數(shù)據(jù)刪除即可。

MySQL持久化和回滾該怎么理解

對于更新的操作會產(chǎn)生update undo log,并且會分更新主鍵的和不更新的主鍵的,假設現(xiàn)在執(zhí)行:

UPDATE user SET name="Sun" WHERE id=1;

MySQL持久化和回滾該怎么理解

這時會把老的記錄寫入新的undo log,讓回滾指針指向新的undo log,它的undo no是1,并且新的undo log會指向老的undo log(undo no=0)。

假設現(xiàn)在執(zhí)行:

UPDATE user SET id=2 WHERE id=1;

MySQL持久化和回滾該怎么理解

對于更新主鍵的操作,會先把原來的數(shù)據(jù)deletemark標識打開,這時并沒有真正的刪除數(shù)據(jù),真正的刪除會交給清理線程去判斷,然后在后面插入一條新的數(shù)據(jù),新的數(shù)據(jù)也會產(chǎn)生undo log,并且undo log的序號會遞增。

可以發(fā)現(xiàn)每次對數(shù)據(jù)的變更都會產(chǎn)生一個undo log,當一條記錄被變更多次時,那么就會產(chǎn)生多條undo log,undo log記錄的是變更前的日志,并且每個undo log的序號是遞增的,那么當要回滾的時候,按照序號依次向前推,就可以找到我們的原始數(shù)據(jù)了。

undo log是如何回滾的?

以上面的例子來說,假設執(zhí)行rollback,那么對應的流程應該是這樣:

  1. 通過undo no=3的日志把id=2的數(shù)據(jù)刪除

  2. 通過undo no=2的日志把id=1的數(shù)據(jù)的deletemark還原成0

  3. 通過undo no=1的日志把id=1的數(shù)據(jù)的name還原成Tom

  4. 通過undo no=0的日志把id=1的數(shù)據(jù)刪除

undo log存在什么地方?

InnoDB對undo log的管理采用段的方式,也就是回滾段,每個回滾段記錄了1024個undo log segment,InnoDB引擎默認支持128個回滾段

mysql> show variables like 'innodb_undo_logs';
+------------------+-------+
| Variable_name    | Value |
+------------------+-------+
| innodb_undo_logs | 128   |
+------------------+-------+

那么能支持的最大并發(fā)事務就是128*1024。每個undo log segment就像維護一個有1024個元素的數(shù)組。

MySQL持久化和回滾該怎么理解

當我們開啟個事務需要寫undo log的時候,就得先去undo log segment中去找到一個空閑的位置,當有空位的時候,就會去申請undo頁,最后會在這個申請到的undo頁中進行undo log的寫入。我們知道m(xù)ysql默認一頁的大小是16k。

mysql> show variables like '%innodb_page_size%';
+------------------+-------+
| Variable_name    | Value |
+------------------+-------+
| innodb_page_size | 16384 |
+------------------+-------+

那么為一個事務就分配一個頁,其實是非常浪費的(除非你的事物非常長),假設你的應用的TPS為1000,那么1s就需要1000個頁,大概需要16M的存儲,1分鐘大概需要1G的存儲...,如果照這樣下去除非mysql清理的非常勤快,否則隨著時間的推移,磁盤空間會增長的非??欤液芏嗫臻g都是浪費的。

于是undo頁就被設計的可以重用了,當事務提交時,并不會立刻刪除undo頁,因為重用,這個undo頁它可能不干凈了,所以這個undo頁可能混雜著其他事務的undo log。undo log在commit后,會被放到一個鏈表中,然后判斷undo頁的使用空間是否小于3/4,如果小于3/4的話,則表示當前的undo頁可以被重用,那么它就不會被回收,其他事務的undo log可以記錄在當前undo頁的后面。由于undo log是離散的,所以清理對應的磁盤空間時,效率不是那么高。

關于MySQL持久化和回滾該怎么理解就分享到這里啦,希望上述內容能夠讓大家有所提升。如果想要學習更多知識,請大家多多留意小編的更新。謝謝大家關注一下創(chuàng)新互聯(lián)網(wǎng)站!

新聞名稱:MySQL持久化和回滾該怎么理解
轉載注明:http://muchs.cn/article8/ihgdip.html

成都網(wǎng)站建設公司_創(chuàng)新互聯(lián),為您提供做網(wǎng)站移動網(wǎng)站建設、小程序開發(fā)、營銷型網(wǎng)站建設網(wǎng)站收錄、定制開發(fā)

廣告

聲明:本網(wǎng)站發(fā)布的內容(圖片、視頻和文字)以用戶投稿、用戶轉載內容為主,如果涉及侵權請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內容未經(jīng)允許不得轉載,或轉載時需注明來源: 創(chuàng)新互聯(lián)

成都seo排名網(wǎng)站優(yōu)化