天天看點

「資料庫」庖丁解 InnoDB 之存儲結構

作者:架構思考
之前兩篇文章《「資料庫」庖丁解InnoDB之REDO LOG》和《「資料庫」庖丁解InnoDB之UNDO LOG》是圍繞 InnoDB 日志進行講解,本片文章針對存儲結構進行講解。歡迎閱讀~

一、InnoDB存儲引擎

InnoDB存儲引擎最早由Innobase Oy公司開發(屬第三方存儲引擎)。從MySQL 5.5版本開始作為表的預設存儲引擎。該存儲引擎是第一個完整支援ACID事務的MySQL存儲引擎,特點是行鎖設計、支援MVCC、支援外鍵、提供一緻性非鎖定讀,非常适合OLTP場景的應用使用。目前也是應用最廣泛的存儲引擎。

InnoDB存儲引擎架構包含記憶體結構和磁盤結構兩大部分,總體架構圖如下:

8.0版本:

「資料庫」庖丁解 InnoDB 之存儲結構

5.5版本:

「資料庫」庖丁解 InnoDB 之存儲結構

二、InnoDB 存儲結構

2.1 磁盤結構

2.1.1 表空間 Tablespaces

InnoDB存儲引擎的邏輯存儲結構是将所有的資料都被邏輯地放在了一個空間中,這個空間中的檔案就是實際存在的實體檔案(.ibd檔案),即表空間。預設情況下,一個資料庫表占用一個表空間,表空間可以看做是InnoDB存儲引擎邏輯結構的最高層,是以的資料都存放在表空間中,例如:表對應的資料、索引、insert buffer bitmap undo資訊、insert buffer 索引頁、double write buffer files 等都是放在共享表空間中的。

表空間分為系統表空間(ibdata1檔案)(共享表空間)、臨時表空間、正常表空間、Undo表空間和file-per-table表空間(獨立表空間)。系統表空間又包括雙寫緩沖區(Doublewrite buffer)、Change Buffer等

1.系統表空間 System Tablespace

系統表空間可以對應檔案系統上一個或多個實際的檔案,預設情況下, InnoDB會在資料目錄下建立一個名為.ibdata1,大小為 12M的檔案,這個檔案就是對應的系統表空間在檔案系統上的表示。這個檔案是可以自擴充的,當不夠用的時候它會自己增加檔案大小。需要注意的一點是,在一個MySQL伺服器中,系統表空間隻有一份。從MySQL5.5.7到MySQL5.6.6之間的各個版本中,我們表中的資料都會被預設存儲到這個系統表空間。

show variables like '%innodb_data_file_path%'           
「資料庫」庖丁解 InnoDB 之存儲結構

2.獨立表空間

在MySQL5.6.6以及之後的版本中, InnoDB并不會預設的把各個表的資料存儲到系統表空間中,而是為每一個表建立一個獨立表空間,也就是說我們建立了多少個表,就有多少個獨立表空間。使用獨立表空間來存儲表資料的話,會在該表所屬資料庫對應的子目錄下建立一個表示該獨立表空間的檔案,檔案名和表名相同,隻不過添加了一個.ibd的擴充名而已。

show variables like '%innodb_file_per_table%'
           
「資料庫」庖丁解 InnoDB 之存儲結構

獨立表空間隻是存放資料、索引和插入緩沖Bitmap頁,其他類的資料如復原(undo)資訊、插入緩沖索引頁、系統事務資訊、二次寫緩沖等還是存放在原來的系統表空間。

3.其他類型的表空間

随着MySQL的發展,除了上述兩種表空間之外,現在還新提出了一些不同類型的表空間,比如通用表空間 (general tablespace)、undo表空間(undo tablespace)、臨時表空間(temporary tablespace)等

4.表空間結構

表空間又由段(segment)、區( extent)、頁(page)組成,頁是InnoDB磁盤管理的最小機關。在我們執行sql時,不論是查詢還是修改,mysql 總會把資料從磁盤讀取内記憶體中,而且在讀取資料時,不會單獨加在一條資料,而是直接加載資料所在的資料頁到記憶體中。表空間本質上就是一個存放各種頁的頁面池。

「資料庫」庖丁解 InnoDB 之存儲結構

「頁」是InnoDB管理存儲空間的基本機關,也是記憶體和磁盤互動的基本機關。也就是說,哪怕你需要1位元組的資料,InnoDB也會讀取整個頁的資料,InnoDB有很多類型的頁,它們的用處也各不相同。比如:有存放undo日志的頁、有存放INODE資訊的頁、有存放Change Buffer資訊的頁、存放使用者記錄資料的頁(索引頁)等等。

InnoDB預設的頁大小是16KB,在初始化表空間之前可以在配置檔案中進行配置,一旦資料庫初始化完成就不可再變更了。

SHOW VARIABLES LIKE 'innodb_page_size'           
「資料庫」庖丁解 InnoDB 之存儲結構

2.1.2 重寫日志 redo log檔案

redo log記錄資料庫的變更,資料庫崩潰後,會從redo log擷取事務資訊,進行系統恢複。redo log在磁盤上表現為ib_logfile0和ib_logfile1兩個檔案。MySQL會在事務的送出前将redo日志重新整理回磁盤。

在同一時間送出的事務,會采用組送出(group commit)的方式一次性重新整理回磁盤。進而避免一個事務重新整理一次磁盤,提高性能。

2.1.3 Double Write Files 雙寫緩沖檔案

double write 是保障 InnoDB 存儲引擎操作資料頁的可靠性。double write 分為兩部分組成,一部分在記憶體中的 double write buffer, 大小為 2MB,另一部分是實體磁盤上共享表空間中連續的128個資料頁,即2個區大小(同樣是2MB)。

「資料庫」庖丁解 InnoDB 之存儲結構

2.2 記憶體結構

InnoDB存儲引擎是基于磁盤存儲的,并将其中的記錄按照頁的方式進行管理,是以可将其視為基于磁盤的資料庫系統(Disk-base Database)。在資料庫中CPU速度與磁盤速度是有很大差距的,基于磁盤的資料庫系統通常使用緩沖池技術來提高資料庫的整體性能。結構如圖所示:

「資料庫」庖丁解 InnoDB 之存儲結構

2.2.1 緩存池 Buffer Pool

Buffer Pool是InnoDB記憶體中的一塊占比較大的區域,通過記憶體的速度來彌補磁盤速度慢對資料庫性能的影響。在資料庫中進行讀取頁的操作,首先将從磁盤讀到的頁放在緩沖池中,這個過程稱為将頁”FIX”在緩沖池中,下次再讀到相同的頁時,首先判斷該頁是否在緩沖池中,若在緩沖池中,直接讀取該頁,否則讀取磁盤上的頁。

對于資料庫中的頁的修改操作,首先修改在緩沖池中的頁,然後再以一定頻率重新整理到磁盤上,這裡需要注意的是,頁從緩沖池重新整理回磁盤的操作并不是在每次頁發生更新時觸發,而是通過一種稱為Checkpoint的機制重新整理回磁盤。

緩存區緩存的資料頁類型有:索引頁,資料頁,undo頁,插入緩沖(change buffer),自适應哈希索引(adaptive hash index),InnoDB存儲鎖資訊(lock info),資料字典資訊(data dictionary)。資料頁和索引頁占據了緩沖池很大部分。

InnoDB1.0.x版本開始,允許有多個緩沖池執行個體,每個頁根據哈希值平均配置設定到不同緩沖池的執行個體中,這樣可以減少資料庫内部資源競争,增加資料庫的并發處理能力。

show variables like 'innodb_buffer_pool_instances'           
「資料庫」庖丁解 InnoDB 之存儲結構

整個Buffer Pool的說明用一張圖來概括如下:

「資料庫」庖丁解 InnoDB 之存儲結構

1.LRU List,Free List和Flush List——管理InnoDB記憶體區域

為了緩存管理的效率,緩沖池被實作為頁連結清單,采用三個連結清單維護記憶體頁,而記憶體頁也是以對應 3 種狀态: Free 尚未使用; Clean 已使用但未修改; Dirty(髒頁)已修改;Free頁隻位于Free List,而Clean和Dirty頁同時位于LRU List,Dirty頁隻存在于Flush List;

「資料庫」庖丁解 InnoDB 之存儲結構

1)LRU List:

資料庫中的緩沖池是通過LRU(Latest Recent Used,最近最少使用)算法來進行管理的。即最頻繁使用的頁在LRU清單的前端,而最少使用的頁在LRU清單的尾端。當緩沖池不能存放新讀取到的頁時,将首先釋放LRU清單中尾端的頁。

在InnoDB存儲引擎中,緩沖池中頁的大小預設為16KB,同樣使用LRU算法對緩沖池進行管理。稍有不同的是InnoDB存儲引擎對傳統的LRU算法做了一些優化。在InnoDB的存儲引擎中,LRU清單中還加入了midpoint位置。新讀取到的頁,雖然是最新通路的頁,但并不是直接放入到LRU清單的首部,而是放入到LRU清單的midpoint位置。這個算法在InnoDB存儲引擎下稱為midpoint insertion strategy。在預設配置下,該位置在LRU清單長度的5/8處。

「資料庫」庖丁解 InnoDB 之存儲結構
SHOW VARIABLES LIKE'innodb_old_blocks_pct'           
「資料庫」庖丁解 InnoDB 之存儲結構

參數innodb_old_blocks_pct預設值為37,表示新讀取的頁插入到LRU清單尾端的37%的位置(差不多3/8的位置)。在InnoDB存儲引擎中,把midpoint之後的清單稱為old清單,之前的清單稱為new清單。可以簡單地了解為new清單中的頁都是最為活躍的熱點資料

  • 那為什麼不采用樸素的LRU算法,直接将讀取的頁放入到LRU清單的首部呢?

這是因為若直接将讀取到的頁放入到LRU的首部,那麼某些SQL操作可能會使緩沖池中的頁被重新整理出,進而影響緩沖池的效率。常見的這類操作為索引或資料的掃描操作。這類操作需要通路表中的許多頁,甚至是全部的頁,而這些頁通常來說又僅在這次查詢操作中需要,并不是活躍的熱點資料。如果頁被放入LRU清單的首部,那麼非常可能将所需要的熱點資料頁從LRU清單中移除,而在下一次需要讀取該頁時,InnoDB存儲引擎需要再次通路磁盤。

  • 解決熱點資料被移除LRU清單

InnoDB存儲引擎引入了另一個參數來進一步管理LRU清單,這個參數是innodb_old_blocks_time,用于表示頁讀取到mid位置後需要等待多久才會被加入到LRU清單的熱端,通過這個方法盡可能使LRU清單中熱點資料不被刷出。

SHOW VARIABLES LIKE'innodb_old_blocks_time'           
「資料庫」庖丁解 InnoDB 之存儲結構

當有新的資料從磁盤查詢到記憶體時,會寫入到 old sub list 的頭部,當此資料再次被查詢的時候,即在 old sublist 中命中之後,才會放入 new sublist 的頭部。當頁從LRU清單的old部分加入到new部分時,稱此時發生的操作為page made young;如果因為innodb_old_blocks_time的設定導緻頁沒有從old部分移動到new部分的操作,稱為page not made young。

通過指令SHOW ENGINE INNODB STATUS可以觀察到如下内容:

SHOW ENGINE INNODB STATUS

----------------------
BUFFER POOL AND MEMORY
----------------------
Total large memory allocated 137428992
Dictionary memory allocated 10620037
Buffer pool size   8191  // 表示目前緩沖池中記憶體頁的數量,記憶體池的大小=Buffer pool size*16KB
Free buffers       1025  //表示目前FREE清單中頁的數量;
Database pages     6985  //LRU清單中頁的數量;
Old database pages 2558  //
Modified db pages  0     //顯示了髒頁的數量;
Pending reads      0
Pending writes: LRU 0, flush list 0, single page 0
Pages made young 4656751, not young 61021911  //表示是否發生了頁在LRU隊列上的移動;
0.00 youngs/s, 0.00 non-youngs/s   //表示每秒兩類操作發生的次數;
Pages read 1036977, created 686192, written 21243071
0.00 reads/s, 0.00 creates/s, 0.28 writes/s
//表示緩沖池的命中率,正常情況下命中率如果低于95%,則需要觀察是否因為全表掃描引起了LRU隊列被污染的問題
Buffer pool hit rate 1000 / 1000, young-making rate 0 / 1000 not 0 / 1000 
Pages read ahead 0.00/s, evicted without access 0.00/s, Random read ahead 0.00/s
LRU len: 6985, unzip_LRU len: 0
I/O sum[17]:cur[0], unzip sum[0]:cur[0]           
「資料庫」庖丁解 InnoDB 之存儲結構
  • 頁壓縮功能

InnoDB存儲引擎從1.0.x版本開始支援壓縮頁的功能,即将原本16KB的頁壓縮為1KB、2KB、4KB和8KB。而由于頁的大小發生了變化,LRU清單也有了些許的改變。對于非16KB的頁,是通過unzip_LRU清單進行管理的,LRU中的頁包含了unzip_LRU清單中的頁。

對于壓縮頁的表,每個表的壓縮比率可能各不相同。可能存在有的表頁大小為8KB,有的表頁大小為2KB的情況。unzip_LRU是怎樣從緩沖池中配置設定記憶體的呢?

首先,在unzip_LRU清單中對不同壓縮頁大小的頁進行分别管理。其次,通過夥伴算法進行記憶體的配置設定。例如對需要從緩沖池中申請頁為4KB的大小,其過程如下:

  • 檢查4KB的unzip_LRU清單,檢查是否有可用的空閑頁;
  • 若有,則直接使用;
  • 否則,檢查8KB的unzip_LRU清單;
  • 若能夠得到空閑頁,将頁分成2個4KB頁,存放到4KB的unzip_LRU清單;
  • 若不能得到空閑頁,從LRU清單中申請一個16KB的頁,将頁分為1個8KB的頁、2個4KB的頁,分别存放到對應的unzip_LRU清單中。

2)Free List:

free list 定義是目前沒有被使用的記憶體頁,也就是空閑的記憶體頁,當執行查詢操作時,如果頁已經在 buffer pool 中了,則查詢到直接傳回,如果沒有在 buffer pool,并且 free list 不為空,則會從磁盤中查詢對應的資料,放入 free list 的某一頁中,并且把這頁從 free list 中移除,放入 LRU 隊列中。Flush List中的髒頁在執行了刷盤操作後會将空間還給Free List,通過這種方式可以解決空間碎片化

LRU清單用來管理已經讀取的頁,但當資料庫剛啟動時,LRU清單是空的,即沒有任何的頁。這時頁都存放在Free清單中。當需要從緩沖池中分頁時,首先從Free清單中查找是否有可用的空閑頁,若有則将該頁從Free清單中删除,放入到LRU清單中。否則,根據LRU算法,淘汰LRU清單末尾的頁,将該記憶體空間配置設定給新的頁。

從上面可以看出 【SHOW ENGINE INNODB STATUS】 :

  • Free buffers表示目前Free清單中頁的數量,Database pages表示LRU清單中頁的數量。可能的情況是Free buffers與Database pages的數量之和不等于Buffer pool size。因為緩沖池中的頁還可能會被配置設定給自适應哈希索引、Lock資訊、Change Buffer等頁,而這部分頁不需要LRU算法進行維護,是以不存在于LRU清單中。
  • pages made young顯示了LRU清單中頁移動到前端的次數,youngs/s、non-youngs/s表示每秒這兩類操作的次數。
  • 這裡還有一個重要的觀察變量——Buffer pool hit rate,表示緩沖池的命中率,通常該值不應該小于95%。若發生Buffer pool hit rate的值小于95%這種情況,使用者需要觀察是否是由于全表掃描引起的LRU清單被污染的問題。

3)Flush List:

在LRU清單中的頁被修改後,稱該頁為髒頁(dirty page),即緩沖池中的頁和磁盤上的頁的資料産生了不一緻。這時資料庫會通過CHECKPOINT機制将髒頁重新整理回磁盤,而Flush清單中的頁即為髒頁清單。需要注意的是,髒頁既存在于LRU清單中,也存在于Flush清單中。LRU清單用來管理緩沖池中頁的可用性,Flush清單用來管理将頁重新整理回磁盤,二者互不影響。

Flush List中的髒頁在執行了刷盤操作後會将空間還給Free List。

同LRU清單一樣,Flush清單也可以通過指令SHOW ENGINE INNODB STATUS來檢視,前面例子中Modified db pages 就顯示了髒頁的數量。

2.Checkpoint技術

資料庫在發生增删查改操作的時候,都是先在buffer pool中完成的,為了提高事物操作的效率,buffer pool中修改之後的資料,并沒有立即寫入到磁盤,這有可能會導緻記憶體中資料與磁盤中的資料産生不一緻的情況。

倘若每次一個頁的變化,就将新頁的版本重新整理到磁盤,那麼這個開銷是非常大的,若熱點資料集中在某幾個頁中,那麼資料庫的性能就會變得非常差。同時,如果在從緩沖池将頁的的新版本重新整理到磁盤時發生了當機,那麼資料就不能恢複了,為了避免這種情況,目前事務資料庫系統普遍都采用了Write Ahead Log政策,即當事務送出時,先寫重做日志,再修改頁,當由于發生當機而導緻資料丢失時,可以通過重做日志來完成資料的恢複。這也是事務ACID中D(Durability持久性)的要求。

checkpoint的作用:

  • 縮短資料庫的恢複時間
  • 緩沖池不夠用時,将髒頁重新整理到磁盤
  • 重做日志不可用時,重新整理髒頁

    checkpoint的分類

  • sharp checkpoint:在關閉資料庫的時候,将buffer pool中的髒頁全部重新整理到磁盤中。
  • fuzzy checkpoint:資料庫正常運作時,在不同的時機,将部分髒頁寫入磁盤,進重新整理部分髒頁到磁盤,也是為了避免一次重新整理全部的髒頁造成的性能問題。

2.2.2 寫緩沖 Change Buffer

在MySQL5.5之前,叫插入緩沖(Insert Buffer),隻針對INSERT做了優化;現在對DELETE和UPDATE也有效,叫做寫緩沖(Change Buffer)。它是一種應用在非唯一普通索引頁(non-unique secondary index page)不在緩沖池中,對頁進行了寫操作,并不會立刻将磁盤頁加載到緩沖池,而僅僅記錄緩沖變更(Buffer Changes),等未來資料被讀取時,再将資料合并(Merge)恢複到緩沖池中的技術。寫緩沖的目的是降低寫操作的磁盤IO,提升資料庫性能。

資料的修改分為兩個情況:

1.當修改的資料頁在緩沖池時

上文講過,通過LRU、Flush List的管理,資料庫不是直接寫入磁盤中,是先将redo log寫入到磁盤,再通過checkpoint機制,将這些“髒資料頁”同步地寫入磁盤,等于是将這期間發生的n次的落盤合并成了一次落盤。因為有redo log是落盤的,是以即使資料庫崩潰,緩存中的資料頁全部丢失,也可以通過redo log将這些資料頁找回來。

redo log是資料庫用來在崩潰的時候進行資料恢複的日志,redo log的寫入政策可以通過參數控制,并不一定是每一次寫操作之後立即落盤redo log,在部分參數下,redo log可能是每秒集中寫入一次,也有可能采取其他落盤政策,但是無論采用什麼方式,redo log的量都是不會減少的,與資料寫入的覆寫性不同,後一條redo log是不會覆寫前一條的,而是增量形式的,是以寫redo log的操作,等同于是對磁盤某一小塊區域的順序I/O,而不像資料落盤一樣的随機IO在磁盤裡寫入,需要磁盤在多個地方移動磁頭。是以redo log的落盤是IO操作當中消耗較少的一種,比資料直接刷回磁盤要優很多。

2.當修改的資料頁不在緩沖池時,不用寫緩沖至少需要下面的三步:

  • 先把需要的索引頁,從磁盤加載到緩沖池,一次磁盤随機讀操作;
  • 修改緩沖池中的頁,一次記憶體操作;
  • 寫入 redo log ,一次磁盤順序寫操作;

在沒有命中緩沖池的時候,至少多産生一次磁盤IO,對于寫多讀少的業務場景,性能損耗是很高的

加入寫緩沖優化後,流程優化為:

  • 在寫緩沖中記錄這個操作,一次記憶體操作;
  • 寫入redo log,一次磁盤順序寫操作;

其性能與這個索引頁在緩沖池中,相近。

3.如何保證資料的一緻性?

  • 資料庫異常奔潰,能夠從redo log中恢複資料;
  • 寫緩沖不隻是一個記憶體結構,它也會被定期刷盤到寫緩沖系統表空間;
  • 資料讀取時,有另外的流程,将資料合并到緩沖池;

下一次讀到該索引頁:

  • 載入索引頁,緩沖池未命中,這次磁盤IO不可避免;
  • 從寫緩沖讀取相關資訊;
  • 恢複索引頁,放到緩沖池LRU和Flush裡;(在真正被讀取時,才會被加載到緩沖池中)

4.為什麼寫緩沖優化,僅适用于非唯一普通索引頁呢?

InnoDB裡有聚集索引(Clustered Index))和普通索引(Secondary Index)兩種。如果索引設定了唯一(Unique)屬性,在 進行修改操作 時, InnoDB必須進行唯一性檢查 。也就是說, 索引頁即使不在緩沖池,磁盤上的頁讀取無法避免(否則怎麼校驗是否唯一!?)

此時就應該直接把相應的頁放入緩沖池再進行修改。

5.除了資料頁被通路,還有哪些場景會觸發刷寫緩沖中的資料呢?

  • 有一個背景線程,會認為資料庫空閑時;
  • 資料庫緩沖池不夠用時;
  • 資料庫正常關閉時;
  • redo log寫滿時;(幾乎不會出現redo log寫滿,此時整個資料庫處于無法寫入的不可用狀态)

6.什麼業務場景,适合開啟InnoDB的寫緩沖機制?

  • 資料庫大部分是非唯一索引;
  • 業務是寫多讀少,或者不是寫後立刻讀取;
「資料庫」庖丁解 InnoDB 之存儲結構
SHOW VARIABLES LIKE 'innodb_change_buffer_max_size'           
「資料庫」庖丁解 InnoDB 之存儲結構

2.2.3 自适應散列索引 Adaptive Hash Index

自适應哈希索引用于優化對BP資料的查詢。InnoDB存儲引擎會監控對二級索引資料的查找,如果觀察到建立哈希索引可以帶來速度的提升(最近連續被通路三次的資料),則建立哈希索引,自适應哈希索引通過緩沖池的B+樹構造而來,是以建立的速度很快。InnoDB存儲引擎會自動根據通路的頻率和模式來為某些頁建立哈希索引。(在高負載系統下AHI容易産生資源的争用,進而引起一些bug導緻系統受影響甚至崩潰,故建議關閉該功能)

2.2.4 重做日志緩沖區 rodo Log Buffer

重做日志緩沖區,當在MySQL中對InnoDB表進行資料更改時,這些更改首先存儲在InnoDB日志緩沖區的記憶體中,然後再寫入重做日志(redo logs)的InnoDB日志磁盤檔案中。他讓MySQL在崩潰的時候具有了恢複資料的能力,即在資料庫發生意外的時候,可以進行資料恢複;

日志緩沖區log buffer是記憶體存儲區域,用于儲存要寫入磁盤上的日志檔案的資料。日志緩沖區大小由innodb_log_buffer_size 變量定義,預設大小為16MB。

日志緩沖區的内容定期重新整理到磁盤。較大的日志緩沖區可以運作大型事務,而無需在事務送出之前将重做日志資料寫入磁盤。是以,如果有更新,插入或删除許多行的事務,則增加日志緩沖區的大小可以節省磁盤I/O。

這裡還涉及到一個參數 innodb_flush_log_at_trx_commit :控制如何将日志緩沖區的内容寫入并重新整理到磁盤,預設為1,不建議修改

  1. 參數為0時,表示事務commit不立即把 redo log buffer 裡的資料刷入磁盤檔案的,而是依靠 InnoDB 的主線程每秒(此時間由參數innodb_flush_log_at_timeout控制,預設1s)執行一次重新整理到磁盤。此時可能你送出事務了,結果 mysql 當機了,然後此時記憶體裡的資料全部丢失。
  2. 參數為1時,表示事務commit後立即把 redo log buffer 裡的資料寫入到os buffer中,并立即執行fsync()操作
  3. 參數為2時,表示事務commit後立即把 redo log buffer 裡的資料寫入到os buffer中,但不立即fsync()SQL執行過程
「資料庫」庖丁解 InnoDB 之存儲結構

什麼是binlog

binlog是一個二進制格式的檔案,用于記錄使用者對資料庫更新的SQL語句資訊,預設情況下,binlog是二進制格式的,不能使用文本工具的指令進行檢視,而是使用mysqlbinlog解析檢視。

binlog的功能

當資料寫入到資料庫的時候,會同時把更新的SQL語句寫入到相應的binlog檔案裡面,同時在使用mysqldump進行備份的時候,隻是對一段時間的資料進行了全局備份,但是如果備份後發現資料庫伺服器産生故障,這個時候就要用到binlog日志了。

binlog和redolog的差別:

  1. redo log是在InnoDB存儲引擎層産生,而binlog是mysql資料庫的上層産生,而且binlog是二進制格式的日志,不僅僅針對InnoDB存儲引擎。
  2. 兩種日志記錄的内容形式不同,MySQL的binlog是邏輯日志,而InnoDB存儲引擎層面的重做日志是實體日志。
  3. 兩種日志與記錄寫入磁盤的時間點不同,二進制日志隻在事物送出完成後進行一次寫入,而redo log的重做日志在事物的進行過程中不斷地被寫入。
  4. binlog不是循環使用,在寫滿或者重新開機之後,會生成新的binlog檔案,但是redo log是循環使用的。

三、InnoDB 存儲特性

  1. 寫緩沖 Change Buffer
  2. 兩次寫 Double Write

InnoDB在把Dirty 髒頁寫回到表空間之前,在記憶體中會線拷貝到連續的記憶體空間double write buffer緩沖區,然後再把它們寫到一個叫doublewrite buffer file的連續磁盤存儲區域内,在寫doublewrite buffer file完成後,InnoDB才會把Dirty pages寫到data file的适當的位置。如果在寫page的過程中發生意外崩潰,InnoDB在稍後的恢複過程中在doublewrite buffer file中找到完好的page副本用于恢複。

「資料庫」庖丁解 InnoDB 之存儲結構

為什麼需要雙寫?

InnoDB 的Page Size一般是16KB,其資料校驗也是針對這16KB來計算的,将資料寫入到磁盤是以Page為機關進行操作的。而計算機硬體和作業系統,寫檔案是以4KB(512位元組)作為機關的,不能保證MySQL資料頁面16KB的一次性原子寫。試想,在某個Dirty Page flush的過程中,發生了系統斷電(或者OS崩潰),16K的資料隻有部分被寫到磁盤上,隻有一部分寫是成功的,這種現象被稱為partial page writes。在出現磁盤崩潰的時候,InnoDB 引擎會從共享表空間中的doublewrite找到該頁的一個副本,将其複制到表空間檔案,再應用重做日志,保障 InnoDB 存儲引擎操作資料頁的可靠性。

為什麼不能使用redo log 解決partial page writes?

一旦partial page writes發生,那麼在InnoDB恢複時就很尴尬:redo log的頁大小一般設計為512個位元組,是以redo log page本身不會發生break page。用redo log來解決partial write 理論上是可行的,不過innodb的redo log是實體邏輯日志,并不是純實體日志,是以發生partial write後崩潰恢複過程中不能直接應用redo log ,innodb發現break page後實際上會報錯。實體邏輯日志不是完全幂等的,這取決于重做日志類型,對于INSERT産生的日志其不是幂等的。

**

兩次寫的工作流程**

double write由兩部分組成,一部分是InnoDB記憶體中的double write buffer,大小為2MB,另一部分是實體磁盤上的ibdata,系統表空間中大小為2MB,共128個連續的Page(2*1024/16KB=128),即兩個分區(extend)一個段(segment)。其中120個頁用于批量重新整理髒頁(如LRU LIST重新整理與FLUSH LIST重新整理這兩種重新整理政策),另外8個頁用于單頁重新整理(Single Page Flush)。做區分的原因是批量刷髒是背景線程做的,不影響前台線程。而單頁重新整理是使用者線程發起的,需要盡快的刷髒頁并替換出一個空閑頁出來。

InnoDB重新整理(寫出)緩沖區中的資料頁時采用的是一次寫多個頁的方式:

  • 多個頁就可以先順序寫入到double write buffer,并調用fsync()保證這些資料被重新整理到double write磁盤(ibdata)。
  • 然後資料頁調用fsync()被重新整理到實際存儲位置;
  • 故障恢複時InnoDB檢查double write Buffer與資料頁原存儲位置的内容,若double write頁處于頁斷裂狀态,則簡單的丢棄;若資料頁不一緻,則從double write頁還原。
「資料庫」庖丁解 InnoDB 之存儲結構

由于double write頁落盤與資料頁落盤在不同的時間點,不會出現double write頁和資料頁同時發生斷裂的情況,是以doublewrite技術可以解決頁斷裂問題,進而保證了重做日志能順利進行,資料庫能恢複到一緻的狀态。

3.自适應哈希索引 Adaptive Hash Index

文章來源:https://juejin.cn/post/7253816086679846972