本文主要介紹innodb重做日志(重做日志撤銷日志binlog),下面一起看看innodb重做日志(重做日志撤銷日志binlog)相關(guān)資訊。
binlog是mysql服務(wù)器層的日志,所有的mysql存儲(chǔ)引擎都支持binlog。binlog可以支持主從復(fù)制和數(shù)據(jù)恢復(fù),但對(duì)事務(wù)的acid特性支持較差。innodb存儲(chǔ)引擎引入了redolog和undolog事務(wù)日志,以提高事務(wù)場(chǎng)景中的數(shù)據(jù)庫性能。本文將介紹redolog和undolog。
redolog和undologchang測(cè)試數(shù)據(jù)插入數(shù)據(jù)庫:
創(chuàng)建表user_info( id int主鍵,name varchar(255));插入到user_info(id,name)值(1 ;ls );查詢語句的執(zhí)行過程如果我們需要查詢id=1的用戶的信息,可以通過下面的sql語句進(jìn)行查詢:
select * from user_info其中id = 1;這個(gè)簡(jiǎn)單的查詢語句之后mysql做了什么?如下圖所示,mysql執(zhí)行sql查詢語句的過程包括以下步驟:
連接器:客戶端與mysql服務(wù)器建立連接,驗(yàn)證用戶名、密碼等信息;查詢緩存:如果sql語句是查詢語句,檢查查詢語句是否命中緩存;分析器:分析sql語句的詞法和語法,判斷sql語句的類型和對(duì)應(yīng)的表等信息;優(yōu)化器:優(yōu)化sql語句并選擇適當(dāng)?shù)乃饕?;?zhí)行器:在對(duì)應(yīng)的mysql引擎上執(zhí)行sql查詢語句,返回查詢結(jié)果;update語句的執(zhí)行過程,如果我們不 t不需要查詢用戶信息,但是想將id=1的記錄中的用戶名更新為zs,我們可以通過下面的sql語句進(jìn)行更新:
更新用戶信息集名稱= zs 其中id = 1;類似于上面文章中的查詢語句,mysql會(huì)先通過connector建立數(shù)據(jù)庫連接,然后通過analyzer、optimizer和executor找到需要更新數(shù)據(jù)的行,然后更新數(shù)據(jù)。
與查詢過程不同,更新過程還涉及changebuffer和兩個(gè)重要的日志模塊:binlog和redolog。binlog和changebuffer的角色在上一篇文章中已經(jīng)介紹過了。如上所述,binlog用于主從復(fù)制和數(shù)據(jù)恢復(fù),changebuffer用于緩存數(shù)據(jù)庫中數(shù)據(jù)的操作,redolog是本文介紹的主角。
changebuffer技術(shù)對(duì)于上面的update語句,如果沒有redolog,innodb引擎會(huì)根據(jù)索引找到id=1的用戶記錄,將記錄加載到內(nèi)存中,然后修改內(nèi)存中的數(shù)據(jù)事務(wù),寫回磁盤。如果數(shù)據(jù)庫數(shù)據(jù)更新的頻率很低,那么這種更新對(duì)于數(shù)據(jù)庫來說是可以接受的,但是在更新非常頻繁的情況下,大量的離散io會(huì)成為數(shù)據(jù)庫的瓶頸,影響數(shù)據(jù)庫的性能。
在頻繁更新的場(chǎng)景下,如何降低磁盤的io,保證事務(wù)?這涉及到我們?cè)谏弦黄恼轮薪榻B的changebuffer技術(shù)。innodb不會(huì)立即將數(shù)據(jù)更改操作寫入磁盤,而是將這些操作緩存在changebuffer中的數(shù)據(jù)頁上,數(shù)據(jù)庫會(huì)找到合適的機(jī)會(huì)將操作合并到數(shù)據(jù)庫中。
通過changebuffer技術(shù),我們可以將多個(gè)離散的數(shù)據(jù)庫訪問合并成一個(gè)數(shù)據(jù)庫訪問,并且用戶 的更新線程不需要實(shí)際訪問磁盤,這大大提高了數(shù)據(jù)庫性能。
沃爾科技,然而,我不 t不知道大家有沒有注意到changebuffer有一個(gè)很大的問題:如果innodb實(shí)例在運(yùn)行過程中斷電,changebuffer中的緩存就會(huì)丟失,從而造成數(shù)據(jù)庫數(shù)據(jù)的不一致,影響數(shù)據(jù)庫事務(wù)的原子性和一致性。
保證數(shù)據(jù)庫中事務(wù)的原子性和一致性的一般方案是采用wal(預(yù)寫日志記錄)技術(shù)。在使用wal的系統(tǒng)中,所有的修改首先被寫入日志,然后應(yīng)用到系統(tǒng)狀態(tài)。日志通常包含兩部分信息:重做和撤消。
重做日志稱為重做日志。每當(dāng)有操作時(shí),都會(huì)在數(shù)據(jù)發(fā)生變化前將操作寫入redolog,這樣當(dāng)停電時(shí),系統(tǒng)重啟后還能繼續(xù)運(yùn)行。undolog稱為undo log,在執(zhí)行中途某些變更無法完成時(shí),可以根據(jù)undo log恢復(fù)到變更之間的狀態(tài);wal技術(shù)在mysql 的innodb引擎,所以innodb存儲(chǔ)引擎包含日志的兩個(gè)部分:redolog和undolog。
如何保證提交的事務(wù)不會(huì)丟失?解決這個(gè)問題比較簡(jiǎn)單。innodb有一個(gè)logforceatcommit機(jī)制。提交事務(wù)時(shí),與該事務(wù)相關(guān)的重做日志數(shù)據(jù)(包括提交記錄)必須從日志緩沖區(qū)寫入重做日志文件。只有當(dāng)事務(wù)成功提交時(shí),信號(hào)才能發(fā)送到用戶進(jìn)程。通過這種機(jī)制,可以保證即使這個(gè)提交事務(wù)中的changebuffer有一部分沒有被寫入數(shù)據(jù)文件,也會(huì)發(fā)生實(shí)例故障,在實(shí)例恢復(fù)時(shí),可以通過redolog的信息前滾不一致的數(shù)據(jù)。
重做日志和二進(jìn)制日志的比較重做日志和二進(jìn)制日志不同。雖然innodb表的很多操作也記錄在binlog中,并且可以實(shí)現(xiàn)重做的功能,但是兩者有很大的區(qū)別。
binlog在存儲(chǔ)引擎的上層生成。無論什么存儲(chǔ)引擎,修改數(shù)據(jù)庫時(shí)都會(huì)生成二進(jìn)制日志。redolog由innodb引擎層生成,只記錄存儲(chǔ)引擎中表的變化;binlog是記錄數(shù)據(jù)變化的邏輯語句,比如一行數(shù)據(jù)的變化或者這個(gè)變化的sql語句。redolog是物理格式的日志,記錄數(shù)據(jù)庫中每個(gè)頁面的修改情況;binlog只寫入日志 文件 每次提交事務(wù)時(shí)在緩存中保存一次(對(duì)于非事務(wù)性表操作,在每次成功執(zhí)行語句后直接寫入)。重做日志在數(shù)據(jù)準(zhǔn)備修改之前寫入緩存中的重做日志,然后修改緩存中的數(shù)據(jù);而且保證了在發(fā)出事務(wù)提交指令時(shí),先將磁盤日志寫入緩存中的redolog,完成寫入后才執(zhí)行提交動(dòng)作;binlog在提交時(shí)只寫一次,所以binlog的記錄和提交順序有關(guān),每次提交對(duì)應(yīng)一條記錄。重做日志是對(duì)記錄的物理頁面的修改。redolog文件中的同一個(gè)事務(wù)可能被記錄多次,最后提交的事務(wù)記錄將覆蓋所有未提交的事務(wù)記錄。例如,在事務(wù)t1中,可以在重做日志中記錄四個(gè)操作t11、t12、t13和t1,其中t1表示最后一次提交時(shí)的日志記錄,因此對(duì)應(yīng)數(shù)據(jù)頁的最終狀態(tài)是對(duì)應(yīng)于t1的操作結(jié)果。而且redolog是并發(fā)寫入的,不同事務(wù)之間不同版本的記錄會(huì)交替寫入redolog文件。例如,redolog的記錄方法可能如下:t11、t12、t21、t22、t2、t13、t1 *。事務(wù)日志記錄物理頁面的情況,是冪等的,所以日志記錄的極其簡(jiǎn)潔。冪等性是指多次運(yùn)算前后狀態(tài)相同。例如,在插入新行并將其刪除后,狀態(tài)保持不變。二進(jìn)制日志記錄了所有影響數(shù)據(jù)的操作,記錄的內(nèi)容更多。例如,插入一行記錄一次,然后再次刪除該行。
重做日志重做日志由兩部分組成:一部分是內(nèi)存中的日志緩沖區(qū),這部分日志是易變的;第二個(gè)是磁盤上的重做日志文件,它是持久的。
從概念上來說,innodb通過forcelogatcommit機(jī)制實(shí)現(xiàn)了事務(wù)的持久化,即當(dāng)一個(gè)事務(wù)被提交時(shí),該事務(wù)的所有事務(wù)日志都必須寫入磁盤上的redolog文件和undolog文件中才能持久化。
為了保證每個(gè)日志都能被寫入事務(wù)日志文件,每次redolog緩沖區(qū)中的日志被寫入日志文件時(shí),都會(huì)調(diào)用操作系統(tǒng)的fsync操作(即fsync系統(tǒng)調(diào)用)。因?yàn)閙ariadb/mysql工作在用戶空間,所以mariadb/mysql的redolog緩沖區(qū)在用戶空間的內(nèi)存中。要寫入磁盤上的重做日志緩沖區(qū),它必須通過操作系統(tǒng)內(nèi)核空間中的操作系統(tǒng)緩存。調(diào)用fsync的作用是將操作系統(tǒng)緩存中的日志刷到磁盤上的redolog文件中。
redolog事務(wù)日志的文件名是ib_logfilen,比如ib_logfile0和ib_logfile1。......
redolog將日志從緩存寫入磁盤,如下圖所示:
mysql支持用戶自定義事務(wù)提交時(shí)如何將日志緩存中的日志刷入磁盤文件。它可以由變量innodb_flush_log_at_trx_commit的值來控制。該變量有三個(gè)值:0、1和2,默認(rèn)值為1。但是,請(qǐng)注意,該變量?jī)H控制在提交事務(wù)時(shí)是否將日志緩存刷新到磁盤。
當(dāng)設(shè)置為1時(shí),日志緩存中的日志將在提交事務(wù)時(shí)寫入操作系統(tǒng)緩存,并將調(diào)用fsync保存到磁盤文件中。這樣即使系統(tǒng)崩潰也不會(huì)有數(shù)據(jù)丟失,但是因?yàn)槊看翁峤欢际菍懙酱疟P,io的性能很差。當(dāng)設(shè)置為0時(shí),日志緩存中的日志不會(huì)在提交事務(wù)時(shí)寫入操作系統(tǒng)緩存,而是每秒寫入操作系統(tǒng)緩存,并調(diào)用fsync保存到磁盤文件中。也就是說,設(shè)置為0時(shí),每秒刷新一次寫入磁盤,系統(tǒng)崩潰時(shí)會(huì)丟失1秒的數(shù)據(jù);設(shè)置為2時(shí),事務(wù)只在提交時(shí)寫入操作系統(tǒng)緩存,然后每秒調(diào)用fsync將操作系統(tǒng)緩存中的日志持久化到磁盤文件中;有一個(gè)變量innodb_flush_log_at_timeout,值為1秒,表示日志記錄的頻率。很多人誤以為是在innodb_flush_log_at_trx_commit的值為0和2時(shí)控制1秒的頻率,其實(shí)不是。以測(cè)試時(shí)間設(shè)置頻率。設(shè)置為5和1時(shí),innodb_flush_log_at_trx_commit設(shè)置為0和2時(shí),性能基本不變。這個(gè)頻率是怎么控制的?這將在后面的 刷日志到磁盤的規(guī)則 。
保證一致性在主從復(fù)制結(jié)構(gòu)中,為了保證事務(wù)的持久性和一致性,需要對(duì)日志相關(guān)的變量進(jìn)行如下設(shè)置:
如果啟用了binlog,則設(shè)置sync_binlog=1,即每個(gè)事務(wù)都同步寫入磁盤。始終設(shè)置innodb _ flush _ log _ at _ trx _ commit = 1,即每個(gè)事務(wù)都寫入磁盤。上述兩個(gè)變量的設(shè)置保證了每次提交事務(wù)時(shí),都會(huì)寫入二進(jìn)制日志和事務(wù)日志,提交時(shí)會(huì)刷新到磁盤。
選擇模式1時(shí),因?yàn)槊總€(gè)事務(wù)都提交到磁盤,所以在大量小事務(wù)的場(chǎng)景下,會(huì)影響數(shù)據(jù)庫的性能。
redolog日志塊在innodb存儲(chǔ)引擎中,redolog是分塊存儲(chǔ)的,每個(gè)塊占512字節(jié),稱為redolog日志塊。日志緩存、系統(tǒng)緩存和磁盤中的重做日志文件以這種存儲(chǔ)在512字節(jié)的塊中。
重做日志記錄數(shù)據(jù)頁的更改。當(dāng)需要用超過492字節(jié)的重做日志記錄一個(gè)數(shù)據(jù)頁的變化時(shí),那么將使用多個(gè)重做日志日志塊來記錄該數(shù)據(jù)頁的變化。
關(guān)于redolog日志塊頭的第三部分log_block_first_rec_group,因?yàn)橛袝r(shí)候一個(gè)數(shù)據(jù)頁面生成的日志量超過一個(gè)日志塊,所以需要使用多個(gè)日志塊來記錄頁面的相關(guān)日志。比如一個(gè)數(shù)據(jù)頁生成一個(gè)552字節(jié)的日志卷,需要占用兩個(gè)日志塊,第一個(gè)日志塊占用492字節(jié),第二個(gè)日志塊占用60字節(jié),那么對(duì)于第二個(gè)日志塊,其第一個(gè)日志的起始位置是73字節(jié)(60 ^ 12)。如果log_block_first_rec_group的值等于log_block_hdr_data_len,說明這個(gè)日志塊中沒有新開始的日志塊,也就是說這個(gè)日志塊是用來延續(xù)前面的日志塊的。日志末尾只有一部分:log_block_trl_no,等于塊頭的log_block_hdr_no。
內(nèi)存中的重做日志緩存和磁盤中的重做日志文件由多個(gè)日志塊組成,如下圖所示:
重做日志組重做日志組由多個(gè)大小相同的重做日志文件組成。組中重做日志文件的數(shù)量由變量innodb_log_files_group決定,默認(rèn)值為2。也就是說,兩個(gè)重做日志文件形成一個(gè)重做日志組。這個(gè)組是一個(gè)邏輯概念,沒有真實(shí)的文件來表示它是一個(gè)組,但是組的目錄可以用變量innodb_log_group_home_dir來定義,redolog文件會(huì)放在這個(gè)目錄下(默認(rèn)在datadir下)。
mysql顯示全局變量,如 innodb _ log % |變量名稱|值|| innodb _ log _ buffer _ size | 16777216 | | innodb _ log _ checksums | on | | innodb _ log _ compressed _ pages | on | | innodb _ log _ file _ size | 50331648 | | innodb _ log _ files _ in _ group | 2 | | innodb _ log _ group _ home _ dir |。/| | innodb _ log _ write _ ahead _ size | 8192 |集合中的7行(0.06秒)root@b48c:/var/lib/mysql# lsl ib *rwr1 mysql root 407 oct 21 09 : 36 ib _ buffer _ poolrwr1 mysql mysql 50331648 oct 26 09 : 00 ib _ log file 0rwr1m ysql mysql根據(jù)目錄,有兩個(gè)以ib_logfile開頭的文件,是redolog日志組中的redolog文件,它們的大小完全相同,等于變量innodb_log_file_size定義的值。當(dāng)innodb_file_per_table未打開時(shí),ibdata1文件是一個(gè)共享表空間文件,對(duì)應(yīng)于。打開innodb_file_per_table時(shí)的ibd文件。
當(dāng)innodb將日志緩存中的redolog日志塊刷入redolog文件時(shí),會(huì)以額外寫入的形式輪流寫入。也就是說,在第一個(gè)重做日志文件(ib_logfile0)的末尾寫入,然后寫入第二個(gè)重做日志文件(ib_logfile1),直到寫滿為止。當(dāng)?shù)诙€(gè)重做日志文件已滿時(shí),第一個(gè)重做日志文件的一部分將被清空以繼續(xù)寫入。
因?yàn)槿罩揪彺嬷械娜罩颈凰⒌搅藃edolog文件中,所以在redolog文件中記錄日志的也是redolog日志塊的。redolog文件的大小對(duì)innodb的性能有很大的影響。如果設(shè)置得太大,需要很長(zhǎng)時(shí)間恢復(fù),如果設(shè)置得太小,會(huì)導(dǎo)致寫redolog時(shí)循環(huán)切換redolog文件。
在每個(gè)組的第一個(gè)重做日志文件中,前2kb記錄四個(gè)特定部分,2kb之后記錄重做日志塊。除了第一個(gè)重做日志文件,重做日志日志組中的其他重做日志文件不會(huì)記錄2kb,但會(huì)為2kb騰出空間。
重做日志文件格式innodb存儲(chǔ)引擎將數(shù)據(jù)存儲(chǔ)在一個(gè)頁面中,因此重做日志也基于頁面格式進(jìn)行記錄。默認(rèn)情況下,innodb的頁面大小為16kb(由變量innodb_page_size控制),一個(gè)頁面可以存儲(chǔ)多個(gè)重做日志塊(每個(gè)512字節(jié)),重做日志塊記錄數(shù)據(jù)頁面的變化。
重做日志塊的492字節(jié)部分是重做日志內(nèi)容,重做日志內(nèi)容的格式分為四個(gè)部分:
redo_log_type:占用1個(gè)字節(jié),表示重做日志的日志類型;space:表空間的id。壓縮后,占用的空間可能小于4字節(jié);page_no:表示頁面的偏移量,也是壓縮的;redo_log_body表示每個(gè)重做日志的數(shù)據(jù)部分,還原時(shí)會(huì)調(diào)用相應(yīng)的函數(shù)進(jìn)行解析。redolog記錄格式redolog的本質(zhì)是記錄事務(wù)對(duì)數(shù)據(jù)庫做了哪些更改。innodb 美國(guó)設(shè)計(jì)師專注于事物為數(shù)據(jù)庫的不改場(chǎng)景定義了多種類型的重做日志,但大多數(shù)類型的重做日志具有以下一般結(jié)構(gòu):
各部分的詳細(xì)解釋如下:
類型:此重做日志的類型。在這個(gè)版本的mysql 5.7.21中,innodb中有53種不同類型的重做日志,后面會(huì)詳細(xì)介紹不同類型的重做日志??臻gid:表空間id。頁碼:頁碼。數(shù)據(jù):這個(gè)重做日志的具體內(nèi)容。我贏了。;本文不介紹重做日志的更詳細(xì)的格式。有興趣的可以自己找文檔。此時(shí)我們應(yīng)該知道,如果我們使用insert語句向數(shù)據(jù)庫中插入一條記錄,那么redolog將記錄指定的值將被設(shè)置在指定空間中指定數(shù)據(jù)頁的指定地址。
當(dāng)redolog磁盤擦洗策略變量innodb_flush_log_at_trx_commit的值為1時(shí),每次提交事務(wù)時(shí),redolog事務(wù)日志都會(huì)被擦洗到磁盤,但innodb不會(huì)只在icommit之后將日志擦洗到磁盤,這只是innodb存儲(chǔ)引擎的規(guī)則之一。有幾種情況會(huì)觸發(fā)日志清理:
提交動(dòng)作發(fā)出時(shí),提交發(fā)出后是否刷日志由變量innodb_flush_log_at_trx_commit控制。每秒刷一次。日志記錄的頻率由變量innodb_flush_log_at_timeout的值決定,默認(rèn)值為1秒。應(yīng)該注意,這個(gè)日志記錄頻率與提交操作無關(guān)。當(dāng)日志緩沖區(qū)中一半以上的內(nèi)存已被使用時(shí)。當(dāng)存在檢查點(diǎn)時(shí),檢查點(diǎn)表示當(dāng)日志在某種程度上被移動(dòng)到磁盤時(shí)日志的lsn位置。dirtydata是在檢查點(diǎn)的緩沖池中未刷到磁盤的臟數(shù)據(jù)。因?yàn)閿?shù)據(jù)和日志都以頁的形式存在,所以臟頁表示臟數(shù)據(jù)和臟日志。最后一節(jié)介紹了日志被刷到磁盤的時(shí)間。不僅日志要刷,臟數(shù)據(jù)頁也要刷。
在innodb中,數(shù)據(jù)清理只有一個(gè)規(guī)則:檢查點(diǎn)。但是有幾種情況會(huì)觸發(fā)檢查點(diǎn)。在任何情況下,觸發(fā)檢查點(diǎn)后,緩存中的臟數(shù)據(jù)頁和臟日志頁都將被刷到磁盤。
innodb存儲(chǔ)引擎中有兩種檢查點(diǎn):
sharp checkpoint:重用redolog文件時(shí)(比如切換日志文件),將redolog中記錄的臟數(shù)據(jù)全部刷到磁盤上。模糊checkpoint:一次只刷一小部分日志到磁盤,而不是所有臟日志。以下情況會(huì)觸發(fā)檢查點(diǎn):主線程檢查點(diǎn):由主線程控制,每秒或每10秒刷一定比例的臟頁到磁盤;flush_lru_list檢查點(diǎn):從mysql5.6開始,可以通過innodb_page_cleaners變量指定負(fù)責(zé)清理臟頁的pagecleaner線程的數(shù)量,這個(gè)線程的目的是保證lru列表有可用的空閑頁;異步/同步刷新檢查點(diǎn):同步或異步磁盤刷。比如還有很多臟頁沒有刷到磁盤上(有多少,比例控制)。這時(shí)候你會(huì)選擇同步刷到磁盤,但這種情況很少發(fā)生;如果臟頁不多,可以選擇異步刷到磁盤;如果臟頁少,可以暫時(shí)不刷到磁盤;臟頁過多檢查點(diǎn):當(dāng)臟頁過多時(shí),檢查點(diǎn)被強(qiáng)制觸發(fā),以確保緩存有足夠的空閑空間。過多的比例由變量innodb_max_dirty_pages_pct控制,mysql 5.6的默認(rèn)值是75,即當(dāng)臟頁占緩沖池的75%時(shí),強(qiáng)制將部分臟頁刷到磁盤。因?yàn)橥瓿膳K頁需要一定的時(shí)間,所以每次磁盤刷完之后,都會(huì)在redolog中標(biāo)記檢查點(diǎn)的位置。mysql停止時(shí)是否將臟數(shù)據(jù)和臟日志刷到磁盤由變量innodb_fast_shutdown={ 0|1|2}控制,默認(rèn)值為1,即mysql停止時(shí)只進(jìn)行一部分purge,大部分的刷新操作被忽略(但至少會(huì)刷日志),剩下的在mysql下次啟動(dòng)時(shí)刷新,實(shí)現(xiàn)fastshutdown。
lsn學(xué)習(xí)lsn被稱為日志序列號(hào)。在innodb存儲(chǔ)引擎中,lsn占用8個(gè)字節(jié),lsn的值會(huì)隨著日志的寫入而增加。分析lsn可以獲得很多關(guān)鍵信息:
數(shù)據(jù)頁的版本信息。寫入的日志總量可以通過lsn開始編號(hào)和結(jié)束編號(hào)來計(jì)算。檢查站的位置。lsn不僅存在于重做日志中,也存在于數(shù)據(jù)頁中。在每個(gè)數(shù)據(jù)頁的頭部,有一個(gè)fil_page_lsn,記錄當(dāng)前頁的最終lsn值。通過比較數(shù)據(jù)頁中的lsn值和重做日志中的lsn值,如果頁中的lsn值小于重做日志中的值,則意味著丟失了一些數(shù)據(jù)。此時(shí)可以通過redolog的記錄恢復(fù)重做。日志中記錄的lsn值。
重做日志的lsn信息可以通過顯示引擎innodb狀態(tài)來查看。mysql版的顯示結(jié)果只有三條記錄,沒有刷新到的頁面。
mysql顯示引擎innodb狀態(tài)...... log 日志序列號(hào)12734454日志刷新達(dá)到12734454頁面刷新達(dá)到12734454最后一個(gè)檢查點(diǎn)為1273 44450掛起日志刷新,0掛起chkp寫入45日志i/o ;完成,0.00日志輸入/輸出 s/秒,其中
日志序列號(hào)是當(dāng)前重做日志中的lsn,通常與緩存中的lsn一致,稱為緩存日志lsn刷新到的日志是磁盤上重做日志文件中的lsn,通常小于日志緩存lsn,稱為磁盤日志lsnpages flushed up是已經(jīng)刷在磁盤數(shù)據(jù)頁上的lsn,稱為磁盤數(shù)據(jù)頁lsn;最后一個(gè)檢查站是lsn的最后一個(gè)檢查站,這是所謂的lsn檢查站。innodb執(zhí)行修改數(shù)據(jù)庫語句的過程如下:
將redolog寫入redolog緩存,并將redolog中對(duì)應(yīng)的lsn記錄為緩存日志lsn如果目標(biāo)數(shù)據(jù)頁在緩存中,則修改緩存中的數(shù)據(jù)頁,并將數(shù)據(jù)頁中的lsn記錄為緩存的數(shù)據(jù)頁lsn;當(dāng)日志刷回磁盤時(shí),在redolog文件中記錄對(duì)應(yīng)的lsn,記為磁盤日志lsn;數(shù)據(jù)頁中的lsn在檢查點(diǎn)臟的時(shí)候緩存,記錄為檢查點(diǎn)lsn;;在checkpoint要刷的數(shù)據(jù)頁很多,刷完所有的數(shù)據(jù)頁需要一定的時(shí)間。每滑一半的數(shù)據(jù)頁都會(huì)記錄當(dāng)前頁所在的lsn,暫且稱為磁盤數(shù)據(jù)頁lsn。下圖顯示了交易過程中各lsn的變化情況:
12 : 00 : 00.000時(shí)間,交易開始。最初,假設(shè)每個(gè)lsn是001;12 : 00 : 00.200,執(zhí)行update語句1,更新緩存日志lsn和緩存數(shù)據(jù)頁lsn,分別加1。,改為001;12 : 00 : 00.400,執(zhí)行update語句2,分別更新緩存日志lsn和緩存數(shù)據(jù)頁lsn,加1改為002;12 : 00 : 00.600,執(zhí)行update語句3,分別更新緩存日志lsn和緩存數(shù)據(jù)頁lsn,加1改為003;12 : 00 : 01.000時(shí)間,檢查點(diǎn),將緩存中的日志和數(shù)據(jù)頁刷回磁盤,將磁盤數(shù)據(jù)頁和磁盤日志的lsn更新為003,將檢查點(diǎn)的lsn更新為003;12 : 00 : 01.200,執(zhí)行update語句3,分別更新緩存日志lsn和緩存數(shù)據(jù)頁lsn,加1改為004;12 : 00 : 01.400,事務(wù)提交,緩存日志寫入磁盤,磁盤日志lsn更新為004;12 : 00 : 02.000時(shí)間,檢查點(diǎn),將緩存中的日志和數(shù)據(jù)頁刷回磁盤,磁盤數(shù)據(jù)頁lsn更新為004,檢查點(diǎn)lsn更新為004;innodb crashsafe在啟動(dòng)innodb時(shí)總是會(huì)恢復(fù),不管它上次是正常關(guān)閉還是異常關(guān)閉。因?yàn)閞edolog記錄的是數(shù)據(jù)頁的物理變化,所以恢復(fù)起來比邏輯日志(比如binlog)要快得多。而且innodb本身也做了一定程度的優(yōu)化,讓恢復(fù)速度更快。
innodb重啟時(shí),checkpoint代表已經(jīng)完全刷到磁盤上數(shù)據(jù)頁的lsn,所以恢復(fù)時(shí)只需要恢復(fù)從checkpoint開始的日志部分。例如,當(dāng)數(shù)據(jù)庫的lsn在最后一個(gè)檢查點(diǎn)為10000時(shí),事務(wù)處于提交狀態(tài)。當(dāng)數(shù)據(jù)庫啟動(dòng)時(shí),將檢查磁盤中數(shù)據(jù)頁的lsn。如果數(shù)據(jù)頁的lsn小于日志中的lsn,恢復(fù)將從檢查點(diǎn)開始。
還有一種情況,停機(jī)前,在檢查點(diǎn)刷盤過程中,數(shù)據(jù)頁的刷盤進(jìn)度超過了日志頁。此時(shí),當(dāng)機(jī)器停機(jī)時(shí),數(shù)據(jù)頁中記錄的lsn將大于日志頁中的記錄,這將在重啟恢復(fù)過程中進(jìn)行檢查。這時(shí)候超出日志進(jìn)度的部分就不會(huì)重做,因?yàn)檫@本身就意味著已經(jīng)做了的事情不需要重做。
此外,事務(wù)日志是冪等的,因此多次操作后得到相同結(jié)果的行為是它在日志中只記錄一次。二進(jìn)制日志不是冪等的,所以會(huì)記錄很多操作,恢復(fù)時(shí)會(huì)多次執(zhí)行二進(jìn)制日志中的記錄,速度慢很多。比如一條記錄中id的初始值為2,通過update設(shè)置為3,再設(shè)置為2,那么事務(wù)日志中記錄的頁面就不變,根本不需要恢復(fù);二進(jìn)制文件將記錄兩個(gè)更新操作,這兩個(gè)操作也將在恢復(fù)過程中執(zhí)行,這比事務(wù)日志恢復(fù)要慢。
redolog相關(guān)變量innodb _ flush _ log _ at _ trx _ commit = { 0 | 1 | 2 }:指定何時(shí)將事務(wù)日志刷到磁盤;默認(rèn)值為1;0表示 日志緩沖區(qū) 將同步到 操作系統(tǒng)緩沖區(qū)和每秒和刷從 操作系統(tǒng)緩沖區(qū)和到磁盤日志文件;1表示每個(gè)事務(wù)提交都將同步日志緩沖區(qū) 致 操作系統(tǒng)緩沖區(qū)和從 操作系統(tǒng)緩沖區(qū)和到磁盤日志文件;2意味著每個(gè)事務(wù)提交都是同步的日志緩沖區(qū) 致 操作系統(tǒng)緩沖區(qū)和但只有來自 操作系統(tǒng)緩沖區(qū)和到磁盤日志文件;innodb _ log _ buffer _ size:log buffer的大小,默認(rèn)值為8minnodb_log_file_size:事務(wù)日志的大小,默認(rèn)值為5minnodb_log_files_group =2:事務(wù)日志組中事務(wù)日志文件的數(shù)量。默認(rèn)值為2 innodb_log_group_home_dir =。/:事務(wù)日志組路徑,當(dāng)前目錄代表數(shù)據(jù)目錄innodb_mirrored_log_groups =1:指定事務(wù)日志組的鏡像組數(shù),但是鏡像功能好像被強(qiáng)制關(guān)閉了,所以只有一個(gè)redolog組。這個(gè)變量在mysql5.7中已經(jīng)被刪除了,undolog的基本概念有兩個(gè)功能:提供回滾和多行版本控制(mvcc)。
當(dāng)數(shù)據(jù)被修改時(shí),wal技術(shù)不僅記錄重做日志,還記錄相應(yīng)的撤消日志。如果事務(wù)由于某種原因失敗或回滾,可以在這個(gè)undolog的幫助下回滾。
undolog和redolog記錄不同的物理日志,它們是邏輯日志??梢哉J(rèn)為,當(dāng)記錄被刪除時(shí),相應(yīng)插入記錄將被記錄在undolog中,反之亦然;當(dāng)記錄被更新時(shí),它記錄相應(yīng)的相反的更新記錄。
當(dāng)執(zhí)行回滾時(shí),可以從undolog中的邏輯記錄讀取相應(yīng)的內(nèi)容并回滾。有時(shí)應(yīng)用于行版本控制時(shí),也是通過undolog來實(shí)現(xiàn)的:當(dāng)讀取一行時(shí),當(dāng)被其他事務(wù)鎖定時(shí),可以從undolog中分析出該行記錄之前的數(shù)據(jù)是什么,從而提供該行的版本信息,讓用戶實(shí)現(xiàn)非鎖定一致讀取。
undolog是以段的形式記錄的,每次撤銷操作在記錄時(shí)都占用一個(gè)undolog段。
此外,undolog還會(huì)產(chǎn)生redolog,因?yàn)閡ndolog也需要實(shí)現(xiàn)持久保護(hù)。
undolog存儲(chǔ)模式innodb存儲(chǔ)引擎采用分段模式管理undo?;貪L段稱為回滾段,每個(gè)回滾段有1024個(gè)undolog段。
之前的老版本只支持一個(gè)回滾段,所以只能記錄1024個(gè)undolog段。后來的mysql5.5可以支持128個(gè)回滾段,也就是128*1024次撤銷操作,可以通過變量innodb _ undo _ logs(5.6版之前這個(gè)變量是innodb_rollback_segments)自定義多少個(gè)回滾段。默認(rèn)值為128。默認(rèn)情況下,undolog存儲(chǔ)在共享表空間中。
root@b48c:/var/lib/mysql# lsl ib *rwr1 mysql root 407 oct 21 09 : 36 ib _ buffer _ poolrwr1 mysql 50331648 oct 26 09 : 00 ib _ log file 0rwr1m ysql mysql 50331648 oct 20 07 : 24 ib _ log file 1rw每個(gè)表的ibd文件。在mysql5.6中,undo的存儲(chǔ)位置也可以由變量innodb_undo_directory定義,默認(rèn)值為 。 對(duì)于datadir。
默認(rèn)rollback段全部寫在一個(gè)文件中,但是通過設(shè)置變量innodb_undo_tablespaces可以均勻分布多少個(gè)文件。這個(gè)變量的默認(rèn)值是0,也就是說,所有的變量都被寫入一個(gè)表空間文件。此變量是靜態(tài)的,只能在數(shù)據(jù)庫示例停止時(shí)修改,例如寫入配置文件或在啟動(dòng)時(shí)獲取相應(yīng)的參數(shù)。
提交事務(wù)時(shí),innodb不會(huì)立即刪除undolog,因?yàn)橐院罂赡軙?huì)用到它。例如,當(dāng)隔離級(jí)別為repeatableread時(shí),事務(wù)在啟動(dòng)時(shí)讀取最新提交的行版本。只要交易沒有完成,就不能刪除行版本,也就是不能刪除undolog。
但是在提交事務(wù)時(shí),該事務(wù)對(duì)應(yīng)的undolog會(huì)被放入刪除列表,以后會(huì)通過purge刪除。并且在事務(wù)提交時(shí),還會(huì)判斷undolog分配的頁面是否可以重用,如果可以重用,就分配給后續(xù)的事務(wù),避免為每個(gè)獨(dú)立的事務(wù)分配獨(dú)立的undolog頁面而浪費(fèi)存儲(chǔ)空間和性能。
刪除和更新操作的結(jié)果由undolog記錄,發(fā)現(xiàn)(插入操作不需要分析,只插入行)。
實(shí)際上刪除操作并不會(huì)直接刪除,只是刪除對(duì)象會(huì)被標(biāo)記為delete,最后的刪除操作會(huì)由purge線程完成。更新分為兩種情況:更新的列是否是主鍵列。如果不是主鍵列,如何更新直接記錄在undolog中,即直接進(jìn)行更新;如果是主鍵列,更新分兩部分執(zhí)行:首先刪除行,然后插入目標(biāo)行。undolog包含舊版本數(shù)據(jù)行的快照信息,存儲(chǔ)在表空間中。
binlog和事務(wù)日志如下圖所示。提交事務(wù)時(shí),寫入日志涉及三個(gè)步驟:
寫入重做日志,在準(zhǔn)備狀態(tài)下寫入二進(jìn)制日志,并將重做日志的狀態(tài)修改為提交。這里我們注意到重做日志的提交過程中引入了兩階段提交。為什么一定要有一個(gè) 兩階段提交和提交;?這是為了使兩個(gè)日志之間的邏輯一致。
因?yàn)閞edolog和binlog是兩個(gè)獨(dú)立的邏輯,如果你不 如果你不需要分兩個(gè)階段提交,你可以先完成重做日志然后再寫binlog,或者采用相反的順序。讓 讓我們看看這兩種方有什么問題,并用上面的更新示例進(jìn)行假設(shè):
先寫redolog,再寫binlog。假設(shè)redolog完成,binlog未完成時(shí),mysql進(jìn)程異常重啟。因?yàn)橐呀?jīng)寫了redolog,系統(tǒng)還是可以崩潰的。把數(shù)據(jù)拿回來。但是binlog中沒有這個(gè)語句的記錄,所以備份日志時(shí)binlog中沒有這個(gè)語句;如果您需要使用此binlog來恢復(fù)臨時(shí)庫,則恢復(fù)的值與原始庫值不同,因?yàn)榇苏Z句的binlog已丟失。先寫binlog,再寫redolog。如果在binlog完成寫入后關(guān)閉,則該行的值不會(huì)更新,因?yàn)閞edolog尚未寫入,并且該事務(wù)在崩潰恢復(fù)后無效。但崩潰前的更新記錄已經(jīng)記錄在binlog中,當(dāng)binlog來恢復(fù)時(shí),又多出了一個(gè)事務(wù),與原庫的值不同。可以看出,兩階段提交是為了防止binlog和redolog不一致。同時(shí)我們也注意到,為了崩潰恢復(fù)的一致性,引入了很多新的東西,也讓系統(tǒng)復(fù)雜了很多,所以有得有失。在兩階段提交redolog和binlog的過程中,2pc事務(wù)的xid(事務(wù)落xid(redolog和binlog)的標(biāo)識(shí))將在兩者刷新后被記錄。如果數(shù)據(jù)庫崩潰正在進(jìn)行中,提交和回滾將在通過xid進(jìn)行恢復(fù)的過程中決定。詳細(xì)步驟見下一段恢復(fù)步驟 。
如果恢復(fù)步驟redolog中的事務(wù)經(jīng)過了兩階段提交中的準(zhǔn)備階段,它將被標(biāo)記為準(zhǔn)備標(biāo)志,如果它經(jīng)過了提交階段,它將被標(biāo)記為提交標(biāo)志(此時(shí)redolog和binlog都已關(guān)閉):
依次掃描redolog,如果redolog中的事務(wù)同時(shí)有準(zhǔn)備和提交標(biāo)記,則直接提交(將redolog磁盤中的數(shù)據(jù)頁復(fù)制到磁盤數(shù)據(jù)頁);如果重做日志事務(wù)只有準(zhǔn)備標(biāo)識(shí)符而沒有提交標(biāo)識(shí)符,則意味著當(dāng)前事務(wù)在提交階段已經(jīng)崩潰,重做日志中的當(dāng)前事務(wù)是否完成是未知的。此時(shí),取redolog中當(dāng)前事務(wù)的xid(落在redolog和binlog中的事務(wù)的標(biāo)識(shí)符)檢查binlog中是否存在此xid:如果binlog中存在當(dāng)前事務(wù)的xid,則提交事務(wù)(將redolog磁盤中的數(shù)據(jù)頁復(fù)制到磁盤數(shù)據(jù)頁)。如果binlog中沒有當(dāng)前事務(wù)的xid,則回滾事務(wù)(使用undolog刪除redolog中對(duì)應(yīng)的事務(wù));我們可以將mysql中redolog和binlog的兩階段提交與廣義上的兩階段提交進(jìn)行比較。廣義來說,如果一個(gè)參與者在時(shí)限內(nèi)沒有收到協(xié)調(diào)器的ack通知,就會(huì)回滾,回滾邏輯要求開發(fā)者在每個(gè)參與者中記錄。mysql兩階段提交通過xid恢復(fù)。回復(fù)。
為了提高性能,組提交通常將多個(gè)相關(guān)的數(shù)據(jù)修改操作放在一個(gè)事務(wù)中,這樣可以避免為每個(gè)修改操作執(zhí)行一個(gè)完整的持久化操作。這種可以被認(rèn)為是一種人為的群體行為。除了將多個(gè)操作合并在一個(gè)事務(wù)中,記錄binlog的操作也可以按照group的思想進(jìn)行優(yōu)化:一次刷新多個(gè)事務(wù)涉及的binlog,而不是一次刷新一個(gè)binlog。
事務(wù)提交時(shí),不僅會(huì)記錄事務(wù)日志,還會(huì)記錄二進(jìn)制日志,但是誰先記錄它們呢?binlog是mysql的上層日志,寫在存儲(chǔ)引擎的事務(wù)日志之前。
在mysql5.6之前,事務(wù)提交時(shí)(即發(fā)出提交指令),mysql收到信號(hào),進(jìn)入提交準(zhǔn)備階段;進(jìn)入準(zhǔn)備階段后,立即將binlog日志寫入內(nèi)存,將binlog日志寫入內(nèi)存后,相當(dāng)于確定提交操作;然后開始在存儲(chǔ)器中寫入事務(wù)日志;最后擦洗binlog日志和事務(wù)日志,擦洗的分別由變量sync_binlog和innodb_flush_log_at_trx_commit控制。
但是為了保證binlog日志和事務(wù)日志的一致性,在提交后的準(zhǔn)備階段會(huì)啟用一個(gè)prepare _ commit _ mutex鎖,以保證它們的順序和一致性。但是這樣會(huì)導(dǎo)致打開binlog日志后group commmit失效,尤其是在主從復(fù)制結(jié)構(gòu)中,binlog日志幾乎都是打開的。在mysql5.6中改進(jìn),提交事務(wù)時(shí),事務(wù)會(huì)放入存儲(chǔ)引擎層上層結(jié)構(gòu)的隊(duì)列中。隊(duì)列中的第一個(gè)事務(wù)稱為領(lǐng)導(dǎo)者,其他事務(wù)稱為追隨者。領(lǐng)導(dǎo)者控制著追隨者的行為。雖然順序還是一樣,先刷binlog,再刷事務(wù)日志,但是機(jī)制完全變了:刪除了原來的prepare_commit_mutex行為,可以保證即使打開了binlog,組提交仍然有效。
mysql5.6中有三個(gè)步驟:刷新階段、同步階段和提交階段:
刷新階段:寫binlog將每筆交易存入內(nèi)存;同步階段:刷內(nèi)存中的binlog日志。如果隊(duì)列中有多個(gè)事務(wù),那么只有一個(gè)fsync操作完成二進(jìn)制日志的磁盤清理操作。這在mysql5.6中被稱為blgc(二進(jìn)制日志組提交);提交階段:領(lǐng)導(dǎo)者按順序調(diào)用存儲(chǔ)引擎層事務(wù)。提交,因?yàn)閕nnodb本來就支持組提交,解決了鎖定prepare_commit_mutex導(dǎo)致的組提交失效問題;binlog在flush階段寫入內(nèi)存,但寫入后并不進(jìn)入sync階段,而是需要等待一定的時(shí)間,幾個(gè)事務(wù)的binlog會(huì)一起進(jìn)入sync階段。等待時(shí)間由變量binlog_max_flush_queue_time決定,默認(rèn)值為0,表示不等待就進(jìn)入同步階段。將該變量設(shè)置為大于0的值的好處是組內(nèi)事務(wù)較多,性能會(huì)更好,但這樣會(huì)導(dǎo)致事務(wù)的響應(yīng)時(shí)間較慢,所以建議不要修改該變量的值,除非有大量的事務(wù)并且不斷被寫入和更新。
進(jìn)入同步階段,binlog會(huì)從內(nèi)存刷到磁盤,刷的多少由變量sync _ binlog控制,就像單個(gè)binlog刷一樣。
當(dāng)一組事務(wù)處于提交階段時(shí),其他新事務(wù)可以處于刷新階段,它們不會(huì)互相阻塞,因此組提交將繼續(xù)生效。當(dāng)然,組提交的性能與隊(duì)列中的事務(wù)數(shù)量有關(guān)。如果隊(duì)列中一次只有一個(gè)事務(wù),則組提交和單獨(dú)提交沒有區(qū)別。當(dāng)隊(duì)列中的事務(wù)越來越多,即提交的事務(wù)越多,組提交的效果就越明顯。
我 m玉壺沈,歡迎關(guān)注我的官方賬號(hào):wzm2zsd。
參考文檔mysql實(shí)戰(zhàn)45講br什么是walbr mysql事務(wù)日志(重做日志和撤銷日志)詳細(xì)分析br你說的——重做日志必須做的事情(一)br。
本文首發(fā)至官方賬號(hào)。保留所有權(quán)利。禁止轉(zhuǎn)載!
標(biāo)簽:
日志交易
了解更多innodb重做日志(重做日志撤銷日志binlog)相關(guān)內(nèi)容請(qǐng)關(guān)注本站點(diǎn)。