MySQL 存儲引擎 - InnoDB 實現原理介紹

存儲結構

內存結構

內存結構主要包括 Buffer Pool、Change Buffer、Adaptive Hash Index和 Log Buffer 四大組件

Buffer Pool

緩衝池,簡稱BP。BP以 Page 頁爲單位,默認大小 16 K。BP的底層採用鏈表數據結構管理 Page。
在 InnnoDB 訪問表記錄和索引是會在 Page 頁中緩存,以後使用可以減少磁盤IO操作,提升效率。

Page管理機制

Page根據狀態可以分爲三種類型:

  • free page
    空閒Page,未被使用
  • clean page
    被使用page,數據沒有被修改過
  • dirty page
    髒頁,被使用page,數據被修改過,頁中數據和磁盤的數據產生了不一致

針對上述三種 Page 類型,InnoDB通過三種鏈表結構來維護和管理

  • free list
    表示空閒緩衝區,管理 free page
  • flush list
    表示需要刷新到磁盤的緩衝區,管理dirty page,內部 page 按照修改時間排序
    dirty page 既存在 Flush 鏈表,也存在 LRU 鏈表中,兩種互不影響。LRU鏈表負責管理 Page 的可用性和釋放,而 Flush 鏈表負責管理髒頁的刷盤操作
  • lru list
    表示正在使用的緩衝區,管理 clean page 和 dirty page
    緩衝區以 midpoint 爲基點,前面稱爲 new 列表區,存放經常訪問的數據(佔63%);後面稱爲 old 列表區,存放使用較少數據(佔37%)
mysql> show engine innodb status \G;

改進型 LRU 算法維護
  • 普通 LRU
    末尾淘汰法,新數據從鏈表頭部加入,釋放空間從末尾淘汰
  • 改進 LRU
    鏈表分爲 new 和 old 兩部分。加入元素時從中間 midpoint 位置插入,如果數據很快被訪問,那麼 page 就會向 new 列表頭部移動,如果數據沒有被訪問,會逐步向 old 尾部移動,等待淘汰

每當有新的 page 數據讀取到 buffer pool時,InnoDB 引擎會判斷是否有足夠空閒頁,如果有,就將 free page 從 free list 列表刪除,放入到 LRU 列表中。沒有空閒頁,就會根據 LRU算法淘汰 LRU 鏈表末尾的頁,將內存空間釋放分配給新的頁

配置參數
mysql> show variables like '%innodb_page_size%';
mysql> select @@innodb_page_size;
+--------------------------------+-----------+
|    Variable_name               |  Value    |
+ -------------------------------+-----------+
|    innodb_page_size            |  16384    |
+ -------------------------------+-----------+

mysql> show variables like '%innodb_old%';
+--------------------------------+-----------+
|    Variable_name               |  Value    |
+ -------------------------------+-----------+
|    innodb_old_blocks_pct       |  37       |
|    innodb_old_blocks_time      |  1000     |
+ -------------------------------+-----------+

mysql> show variables like '%innodb_buffer%';
+-------------------------------------------------+-------------------+
|    Variable_name                                |  Value            |
+ ------------------------------------------------+-------------------+
|    innodb_buffer_pool_chunk_size                |  8388608          |
|    innodb_buffer_pool_dump_at_shutdownsize      |  ON               |
|    innodb_buffer_pool_dump_now                  |  OFF              |
|    innodb_buffer_pool_dump_pct                  |  25               |
|    innodb_buffer_pool_filename                  |  ib_buffer_pool   |
|    innodb_buffer_pool_instances                 |  1                |
|    innodb_buffer_pool_load_abort                |  OFF              |
|    innodb_buffer_pool_load_at_startup           |  ON               |
|    innodb_buffer_pool_load_now                  |  OFF              |
|    innodb_buffer_pool_size                      |  8388608          |
+ ------------------------------------------------+-------------------+

TIPS:將 innodb_buffer_pool_size 設置爲總內存大小的 60% ~ 80%,innodb_buffer_pool_instances 可以設置爲多個,這樣可以避免緩存爭奪

Change Buffer

寫緩衝區,簡稱CB。在進行 DML 操作是,如果 BP 沒有其相應的 Page 數據,並不會立刻將磁盤頁加載到緩衝池,而是在 CB 記錄緩衝變更,等未來數據被讀取時,再將數據合併恢復到 BP 中。

ChangeBuffer佔用 Buffer Pool 空間,默認佔 25%,最大允許佔 50%,可以根據讀寫業務量來進行調整。

mysql> show variable like '%innodb_change_buffer_max_size%'
+-------------------------------------------------+-------------------+
|    Variable_name                                |  Value            |
+ ------------------------------------------------+-------------------+
|    innodb_change_buffer_max_size                |  25               |
+ ------------------------------------------------+-------------------+

mysql> set global innodb_change_buffer_max_size = 20;

當更新一條記錄時,該記錄在 BufferPool 存在,直接在 BufferPool 修改,一次內存操作;如不存在(沒有命中),會直接在 ChangeBuffer 進行一次內存操作(不用再去磁盤查詢,避免一次磁盤IO),當下次查詢記錄是,會先進行磁盤讀取,然後再從 ChangeBuffer 中讀取信息合併,最終載入 BufferPool 中

寫緩衝區僅適用於非唯一普通索引頁。因爲索引設置唯一性後,在進行修改時,InnoDB必須要做唯一性校驗,因此必須查詢磁盤做一次IO操作。此時會直接將記錄查詢到 BufferPool 中,然後在緩衝池修改,因此不會再在 ChangeBuffer 中操作

Adaptive Hash Index

自適應哈希索引,用於優化對 BP 數據的查詢。InnoDB 存儲引擎會監控對錶索引的查找,如果觀察到建立哈希索引可以帶來速度的提升,則建立哈希索引,所以稱之爲自適應。InnoDB 存儲引擎會自動根據訪問的頻率和模式來爲某些頁建立哈希索引

Log Buffer

日誌緩衝區,用來保存要寫入磁盤 log 文件(Redo/Undo)的數據,日誌緩衝區的內容定期刷新到磁盤 log 文件中。日誌緩衝區滿時會自動將其刷新到磁盤,當遇到 BLOB 或多行更新的大事務操作時,增加日誌緩衝區可以節省磁盤I/O

LogBuffer主要是用於記錄 InnoDB引擎日誌,在 DML 操作時會產生 Redo 和 Undo 日誌

LogBuffer空間滿時會自動寫入磁盤,可以通過將 innodb_log_buffer_size 參數調大,減少磁盤I/O頻率
innodb_flush_log_at_trx_commit參數控制日誌刷新行爲,默認爲1

  • 0:每隔1秒寫日誌文件和刷盤操作(寫日誌文件LogBuffer -> OS cache,刷盤 OS cache -> 磁盤文件),最多丟失1秒數據
  • 1 :事務提交,立刻寫日誌文件和刷盤,數據不丟失,但是會頻繁IO操作
  • 2:事務提交,立刻寫日誌文件,每隔1秒進行刷盤操作

磁盤結構

InnoDB磁盤主要包含 Tablespaces、InnoDB Data Dictionary、Doublewrite Buffer、Redo Log 和 Undo Logs

Tablespaces

表空間,用於存儲表結構和數據。

  • 系統表空間(The System Tablespaces)
    包含 InnoDB 數據字典,Doublewrite Buffer、Change Buffer、Undo Logs的存儲區域。
    系統表空間也默認包含任何用戶在系統表空間創建的表數據和索引數據。
    系統表空間是一個共享的表空間,因爲它被多個表共享。
mysql> show variable like '%innodb_data_file_path%'
+-----------------------------------------+-------------------------+
|    Variable_name                        |  Value                  |
+ ----------------------------------------+-------------------------+
|    innodb_data_file_path                |  ibdata1:12M:autoextend |
+ ----------------------------------------+-------------------------+

  • 獨立表空間(File-Per-Table Tablespaces)
    默認開啓,獨立表空間是一個單表表空間,該表創建於自己的數據文件中,而非創建於系統表空間中。
    innodb_file_per_table=ON時,表將創建於獨立表空間,否則創建於系統表空間
    獨立表空間由一個 .ibd 數據文件代表,該文件默認被創建於數據庫目錄中。表空間的表文件支持動態(dynamic)和壓縮(compressed)行格式
mysql> show variable like '%innodb_file_per_table%'
+-----------------------------------------+-------------------------+
|    Variable_name                        |  Value                  |
+ ----------------------------------------+-------------------------+
|    innodb_file_per_table                |  ON                     |
+ ----------------------------------------+-------------------------+

  • 通用表空間(General Tablespaces)
    通用表空間爲通過 create tablespace 語法創建的共享表空間。通用表空間可以創建於 mysql 數據目錄外的其他表空間,其可以容納多張表,且支持所有的行格式
CREATE TABLESPACE ts1 ADD DATAFILE ts1.ibd Engine=InnoDB;   // 創建表空間ts1
CREATE TABLE t1 (c1 INT PRIMARY KEY) TABLESPACE ts1;   // 將表添加到ts1表空間

  • 撤銷表空間(Undo Tablespaces)
    撤銷表空間由一個或多個包含 Undo 日誌文件組成。在 MySQL 5.7 版本之前 Undo 佔用的是 System Tablespaces 共享區,從 5.7 開始將 Undo 從 System Tablespaces 分離了出來。
    InnoDB 使用的 Undo 表空間由 innodb_undo_tablespaces 配置選項控制,默認爲0。
    參數值爲 0 表示使用系統表空間 ibdata1;大於 0 表示使用 undo 表空間 undo_001、undo_002 等
  • 臨時表空間(Temporary Tablespaces)
    分爲 session temporary tablespaces 和 global temporay tablespaces 兩種。
    session temporary tablespaces 存儲的是用戶創建的臨時表和磁盤內部的臨時表
    global temporay tablespaces 存儲用戶臨時表的回滾段 (rollback segments)
    mysql 服務器正常關閉或異常終止時,臨時表空間將被移除,每次啓動時會被重新創建

InnoDB Data Dictionary

數據字典由內部系統表組成,這些表包含用於查找表、索引和表字段等對象的元數據。元數據物理上位於InnoDB系統表空間中。由於歷史原因,數據字典元數據在一定程度上與InnoDB表元數據文件(.frm文件)中存儲的信息重疊

Doublewrite Buffer

雙寫緩衝區位於系統表空間,是一個存儲區域。在 BufferPage 的 page 頁刷新到磁盤真正的位置前,會先將數據存在 Doublewrite 緩衝區。如果在 page 頁寫入過程中出現操作系統、存儲子系統或 mysqld 進程崩潰,InnoDB 可以在崩潰恢復期間從 Doublewrite 緩衝區中找到頁面的一個好備份。默認啓用雙寫緩衝區。

mysql> show variable like '%innodb_doublewrite%'
+-----------------------------------------+-------------------------+
|    Variable_name                        |  Value                  |
+ ----------------------------------------+-------------------------+
|    innodb_doublewrite                   |  ON                     |
+ ----------------------------------------+-------------------------+

MySQL 的 innodb_flush_method 這個參數控制着 InnoDB 數據文件及 redo log 的打開、刷寫模式。

  • fdatasync
    默認選項,使用 default + fsync 操作數據文件和日誌文件,即讀寫都經過操作系統緩存,寫完後再調用 fsync() 函數去異步刷入磁盤
  • O_DSYNC
    使用 O_SYNC 打開日誌文件,即每次寫完日誌文件,操作系統緩存會自動通過 fsync 刷入磁盤;使用 default + fsync 操縱數據文件。
  • O_DIRECT
    數據文件的寫入操作是直接從innodb buffer到磁盤的,並不用通過操作系統的緩存;日誌還是需要經過 OS 緩衝

使用 Doublewrite 緩衝區時建議將 innodb_flush_method 設置爲 O_DIRECT

Redo Log

重做日誌是一種基於磁盤的數據結構,用於在崩潰恢復期間更正不完整事務寫入的數據。MySQL 以循環方式寫入重做日誌文件,記錄 innodb 中所有對 Buffer Pool 修改的日誌。當出現實例故障(像斷電),導致數據未能更新到數據文件,則數據庫重啓時須redo,重新把數據更新到數據文件。讀寫事務在執行的過程中,都會不斷的產生 redo log。默認情況下,重做日誌在磁盤上由兩個名爲 ib_logfile0 和 ib_logfile1 的文件物理表示

Undo Logs

撤銷日誌是在事務開始之前保存的被修改數據的備份,用於例外情況時回滾事務。撤銷日誌屬於邏輯日誌,根據每行記錄進行記錄。撤銷日誌存在於系統表空間、撤銷表空間和臨時表空間中

新版本結構演變

MySQL 5.7版本
  • 將 Undo 日誌表空間從共享表空間 ibdata 文件中分離出來了,可以在安裝 MySQL 時由用戶自行指定文件大小和數量
  • 增加了 temporary 臨時表空間,裏面存儲着臨時表或臨時查詢結果集的數據
  • Buffer Pool 大小可以動態修改,無需重啓數據庫實例
MySQL 8.0版本
  • 將 InnoDB 表的數據字典和 Undo 都從共享表空間 ibdata 中徹底分離出來了,以前需要 ibdata 中數據字典與獨立表空間 ibd 文件中數據字典一致纔行,8.0版本就不需要了。
  • temporary 臨時表空間也可以配置多個物理文件,而且均爲 InnoDB 存儲引擎並能創建索引,這樣加快了處理的速度
  • 用戶可以像 Oracle 數據庫那樣設置一些表空間,每個表空間對應多個物理文件,每個表空間可以給多個表使用,但一個表只能存儲在一個表空間中
  • 將 Doublewrite Buffer 從共享表空間 ibdata 中也分離出來了

線程模型

IO Thread

在 InnoDB 中使用了大量的AIO(Async IO)來做讀寫處理,這樣可以極大提高數據庫的性能。在 InnoDB1.0版本之前共有4個IO Thread,分別是write、read、insert buffer和log thread,後來版本將 read 和 write thread分別增大到4個,一共有10個了

  • read thread:負責讀取操作,將數據從磁盤加載到緩存 page 頁。4個
  • write thread:負責寫操作,將緩存髒頁刷新到磁盤。4個
  • log thread:負責將日誌緩衝區內容刷新到磁盤。1個
  • insert buffer thread:負責將寫緩衝區內容刷新到磁盤。1個
mysql> show engine innodb  status \G;
---------------------
FILE I/O
---------------------
I/O thread 0 state: wait Windows aio (insert buffer thread)
I/O thread 1 state: wait Windows aio (log thread)
I/O thread 2 state: wait Windows aio (read thread)
I/O thread 3 state: wait Windows aio (read thread)
I/O thread 4 state: wait Windows aio (read thread)
I/O thread 5 state: wait Windows aio (read thread)
I/O thread 6 state: wait Windows aio (write thread)
I/O thread 7 state: wait Windows aio (write thread)
I/O thread 8 state: wait Windows aio (write thread)
I/O thread 9 state: wait Windows aio (write thread)

Purge Thread

事務提交之後,其使用的 undo 日誌將不再需要,因此需要 purge thread 回收已經分配的 undo 頁

mysql> show variables like '%innodb_purge_thread%';
+-----------------------------------------+-------------------------+
|    Variable_name                        |  Value                  |
+ ----------------------------------------+-------------------------+
|    innodb_purge_thread                  |  4                      |
+ ----------------------------------------+-------------------------+

Page Cleaner Thread

作用是將髒數據刷新到磁盤,髒數據刷盤後相應的 redo log 也就可以覆蓋,即可以同步數據,又能達到 redo log 循環使用的目的。會調用 write thread 線程處理

mysql> show variables like '%innodb_page_cleaners%';
+-----------------------------------------+-------------------------+
|    Variable_name                        |  Value                  |
+ ----------------------------------------+-------------------------+
|    innodb_page_cleaners                 |  1                      |
+ ----------------------------------------+-------------------------+

Master Thread

Master Thread 是 InnoDB 的主線程,負責調度其他各線程,優先級最高。作用是將緩衝池中的數據異步刷新到磁盤,保證數據的一致性。包括:髒頁的刷新(page cleaner threa)、undo 頁回收(purge thread)、redo日誌刷新(log thread)、合併寫緩衝等。
內部有兩個主處理:

  • 每隔1秒的操作
    刷新日誌緩衝區,刷到磁盤
    合併寫緩衝區數據,根據IO讀寫壓力來決定是否操作
    刷新髒頁數據到磁盤,根據髒頁比例達到75%才操作
mysql> show variables like '%innodb_max_dirty_pages_pct%';
+-----------------------------------------+-------------------------+
|    Variable_name                        |  Value                  |
+ ----------------------------------------+-------------------------+
|    innodb_max_dirty_pages_pct           |  75.000000              |
|    innodb_max_dirty_pages_pct_lwm       |  0.000000               |
+ ----------------------------------------+-------------------------+
mysql> show variables like '%innodb_io_capacity%';
+-----------------------------------------+-------------------------+
|    Variable_name                        |  Value                  |
+ ----------------------------------------+-------------------------+
|    innodb_io_capacity                   |  200                    |
|    innodb_io_capacity_max               |  2000                   |
+ ----------------------------------------+-------------------------+

  • 每隔10秒的操作
    刷新髒頁數據到磁盤
    合併寫緩衝區數據
    刷新日誌緩衝區
    刪除無用的undo頁

數據文件

文件存儲結構

  • Tablespace
    表空間,用於存儲多個 ibd 數據文件,用於存儲表的記錄和索引。一個文件包含多個段

  • Segment
    段,用於管理多個 Extend,分爲數據段(Leaf node segment)、索引段(Non-leaf node segment)、回滾段(Rollback segment)。一個表至少會有兩個segment,一個管理數據,一個管理索引。每多創建一個索引,會多兩個segment。

  • Extend
    區,一個區固定包含64個連續的頁,大小爲1M。當表空間不足時,需要分配新的頁資源,不會按頁分配,而是直接分配一個區。

  • Page
    頁,是文件最基本的單位,用於存儲多個Row行記錄,大小爲16K。包含很多種頁類型,比如數據頁、undo頁,系統頁,事務數據頁,大的BLOB對象頁

  • Row
    行,包含了記錄的字段值,事務ID(Trx id)、滾動指針(Roll pointer)、字段指針(Field pointers)等信息

文件存儲格式

File-Format

在早期的 InnoDB 版本中,文件格式只有一種,隨着 InnoDB 引擎的發展,出現了新文件格式,用於支持新的功能。

  • Antelope
    原始的 InnoDB 文件格式,它支持兩種行格式:COMPACT 和 REDUNDANT。MySQL 5.6及以前版本的默認格式
  • Barracuda
    新文件格式。它支持 InnoDB 的所有行格式,包括新的行格式:COMPRESSED 和 DYNAMIC。MySQL 5.7的默認格式
mysql> show variables like '%innodb_file_format%';
+-----------------------------------------+-------------------------+
|    Variable_name                        |  Value                  |
+ ----------------------------------------+-------------------------+
|    innodb_file_format                   |  Antelope               |
|    innodb_file_format_check             |  ON                     |
|    innodb_file_format_max               |  Antelope               |
+ ----------------------------------------+-------------------------+

Row-Format

表的行格式決定了它的行是如何物理存儲的,這反過來又會影響查詢和 DML 操作的性能。如果在單個 Page 頁中容納更多行,查詢和索引查找可以更快地工作,緩衝池中所需的內存更少,寫入更新時所需的I/O更少

每個表的數據分成若干頁來存儲,每個頁中採用B數結構存儲。如果某些字段信息過長,無法存儲在B數節點中,這時會被單獨分配空間,此時稱爲溢出頁,該字段被稱爲頁外列

InnoDB 存儲引擎支持四種行格式

  • REDUNDANT
    使用 REDUNDANT 行格式,表會將變長列值的前 768 字節存儲在B樹節點的索引記錄中,其他的存儲在溢出頁上。對於大於等於 768 字節的固定長度字段 InnoDB 會轉化爲變長字段,以便能夠在頁外存儲
  • COMPACT
    與 REDUNDANT 行格式相比,COMPACT 行格式減少了約 20% 的行存儲空間,但代價是增加了某些操作的 CPU 使用量。如果系統負載是受緩存命中率和磁盤速度限制,那麼 COMPACT 格式可能更快。如果系統負載受 CPU 速度的限制,那麼 COMPACT 格式可能會慢一些
  • DYNAMIC
    使用 DYNAMIC 行格式,InnoDB 會將表中長可變長度的列值完成存儲在頁外,而索引記錄只包含指向溢出頁的 20 字節指針。大約或等於 768 字節的固定長度字段編碼爲可變長度字段。DYNAMIC 行格式支持大索引前綴,最多可以爲 3072 字節,可以通過 innodb_large_prefix 參數控制
  • COMPRESSED
    COMPRESSED 行格式提供了與 DYNAMIC 行格式相同的存儲特性和功能,但增加了對錶和索引數據壓縮的支持
Row Format Compact Storage Characteristics Enhanced Variable-Length Column Storage Large Index key Prefix Support Compression Support Supported Tablespace Types Required File Format
REDUNANT No No No No system, file-per-table Antelope or Barracuda
COMPACT Yes No No No system, file-per-table Antelope or Barracuda
DYNAMIC Yes Yes Yes No file-per-table Barracuda
COMPRESSED Yes Yes Yes Yes file-per-table Barracuda

在創建表和索引時,文件格式都被用於每個 InnoDB 表數據文件(其名稱與*.ibd匹配)。修改文件格式的方法是重新創建表及其索引,最簡單方法是對要修改的每個表使用如下命令

ALTER TABLE 表名 ROW_FORMAT=格式類型;

查看文件存儲格式

mysql> SHOW TABLE STATUS
            Name : dept
          Engine : InnoDB
         Version : 10
      Row_format : Dynamic
            Rows : 3
  Avg_row_length : 5461
     Data_length : 0
 Max_data_length : 0
    Index_length : 0
       Data_free : 0
  Auto_increment : NULL
     Create_time : 2022-05-04 11:59:24
     Update_time : 2022-05-05 10:29:54
      Check_time : NULL
       Collation : latin1_swedish_ci
        Checksum : NULL
  Create_options : 
         Comment : 

mysql> select * from information_schema.innodb_sys_tables where name = 'dept' \G;
     TABLE_ID : 71
         NAME : dept
         FLAG : 33
       N_COLS : 5
        SPACE : 54
  FILE_FORMAT : Barracuda
   ROW_FORMAT : Dynamic
ZIP_PAGE_SIZE : 0
   SPACE_TYPE : Single

Undo Log

Undo :意爲撤銷或取消,以撤銷操作爲目的,返回指定某個狀態的操作
Undo Log :數據庫事務開始之前,會將要修改的記錄存放在 Undo 日誌裏,當事務回滾時或數據庫崩潰時,可以利用 Undo 日誌,撤銷未提交事務對數據庫產生的影響

產生和銷燬

Undo Log 在事務開始前產生。事務在提交時,並不會立刻刪除 Undo Log,InnoDB 會將該事務對應的 Undo Log 放入到刪除列表中,後續會通過後臺進程 Purge Thread 進行回收處理
Undo Log 屬於邏輯日誌,記錄一個變化過程。例如執行一個 delete,Undo Log 會記錄一個 insert;執行一個 update,Undo Log 會記錄一個相反的 update

存儲

Undo Log 採用段的方式管理和記錄。在 InnoDB 數據文件中包含一種 Rollback Segment 回滾段,內部包含 1024 個 Undo Log Segment。可以通過下面一組參數來控制 Undo Log 存儲

mysql> show variables like '%innodb_undo%';

作用

  • 實現事務的原子性
    Undo Log 是爲了實現事務的原子性而出現的產物。事務處理過程中,如果出現了錯誤或用戶執行了 ROLLBACK 語句,MySQL 可以利用 Undo Log 中的備份將數據恢復到事務開始之前的狀態

  • 實現多版本併發控制(MVCC)
    Undo Log 在 MySQL InnoDB 存儲引擎中用來實現多版本併發控制。事務未提交之前,Undo Log 保存了未提交之前的版本數據,Undo Log 中的數據可作爲數據舊版本快照供其他併發事務進行快照讀

事務A手動開啓事務,執行更新操作,首先會把更新命中的數據備份到 Undo Buffer 中
事務B手動開啓事務,執行查詢操作,會讀取 Undo 日誌數據返回,進行快照讀

Redo Log

Redo :就是重做。以恢復操作爲目的,在數據庫發生意外時重現操作
Redo Log :指事務中修改的任何數據,將最新的數據備份存儲的位置(Redo Log),被稱爲重做日誌

生成和釋放

隨着事務操作的執行,就會生成 Redo Log,在事務提交時會將產生 Redo Log 寫入 Log Buffer,並不是隨着事務的提交就立刻寫入磁盤文件。等事務操作的髒頁寫入到磁盤後,Redo Log 的使命就完成了,Redo Log 佔用的空間就可以重用(被覆蓋寫入)。

工作原理

Redo Log 是爲了實現事務的持久化而出現的產物。防止在發生故障的時間點,尚有髒頁未寫入表的 IBD 文件中,在重啓 MySQL 服務的時候,根據 Redo Log 進行重做,從而達到事務的未入磁盤數據進行持久化這一特性

  • 事務提交,爲什麼不直接持久化 User 到磁盤,而是要通過 Redo Log 來處理
    每次提交事務時,都直接持久化到磁盤,IO頻率過高,效率低下。通常優化方案是,多個事務執行後統一持久化
  • 持久化到 Redo Log 和 直接持久化 User 到磁盤都是 IO 操作,有何區別
    假如有兩個 User (ID 分別爲 1, 8),直接持久化 User,需要分別查找定位到 1 和 8 的用戶信息位置,然後再更新,屬於隨機IO,效率低;而通過 Redo Log 寫時是將這兩個變更信息順序的寫入到 Redo Log 中,屬於順序IO,效率高

寫入機制

Redo Log 文件內容是以順序循環的方式寫入文件,寫滿時則回溯到第一個文件,進行覆蓋寫

  • write pos 是當前記錄的位置,一邊寫一邊後移,寫到最後一個文件末尾後就回到 0 好文件開頭
  • checkpoint 是當前要擦除的位置,也是往後推移並且循環的,擦除記錄前要把記錄更新到數據文件
  • write pos 和 checkpoint 之間還空着的部分,可以用來記錄新的操作。如果 write pos 追上了 checkpoint,表示寫滿,此時不能再執行新的更新,得停下來先擦除一些記錄,把 checkpoint 推進一下

相關配置

每個 InnoDB 存儲引擎至少有 1 個重做日誌文件組(Group),每個文件組至少有 2 個重做日誌文件,默認爲 ib_logfile0 和 ib_logfile1。可以通過參數控制 Redo Log 存儲

mysql> show variables like '%innodb_log%';

Redo Buffer 持久化到 Redo Log 的策略,可以通過 innodb_flush_log_at_trx_commit 設置

  • 0 :每秒提交 Redo Buffer -> OS cache -> flush cache to dis,可能丟失一秒內的事務數據。由後臺 Master 線程每隔 1 秒執行一次操作
  • 1(默認值):每次事務提交執行 Redo Buffer -> OS cache -> flush cache to disk,最安全,性能最差的方式
  • 2 :每次事務提交執行 Redo Buffer -> OS cache,然後由後臺 Master 線程再每隔 1 秒執行 OS cache -> flush cache to disk 的操作

一般建議選擇取值 2, 因爲 MySQL 掛了數據沒有損失,整個服務器掛了纔會損失 1 秒的事務提交數據

Bin Log

二進制日誌(Binary Log),Binlog 是記錄所有數據庫表結構變更以及表數據修改的二進制日誌,不會記錄 SELECT 和 SHOW 這類操作。Binlog 日誌是以事件形式記錄,還包含語句所執行的消耗時間。

使用場景:

  • 主從複製
    在主庫中開啓 Binlog 功能,這樣主庫就可以把 Binlog 傳遞給從庫,從庫拿到 Binlog 後實現數據恢復達到主從數據一致性
  • 數據恢復
    通過 mysqlbinlog 工具來恢復數據

記錄模式

Binlog 文件名默認爲“主機名_binlog-序列號”格式,例如 oak_binlog-000001,也可以在配置文件中指定名稱。

文件記錄模式有:

  • ROW(row-based replication, RBR)
    日誌中會記錄每一行數據被修改的情況,然後再 slave 端對相同的數據進行修改
    優點:能清楚記錄每一個行數據的修改細節,能完全實現主從數據同步和數據的恢復
    缺點:批量操作,會產生大量的日誌,尤其是 alter table 會讓日誌暴漲
  • STATEMENT(statement-based replication,SBR)
    每一條被修改數據的 SQL 都會記錄到 master 的 Binlog 中,slave 在複製的時候 SQL 進程會解析成和原來 master 端執行過的相同的 SQL 再次執行。簡稱 SQL 語句複製。
    優點:日誌量小,減少磁盤IO,提升存儲和恢復速度
    缺點:在某些情況下會導致主從數據不一致,比如 last_insert_id()、now() 等函數
  • MIXED(mixed-based replication, MBR)
    以上兩種模式的混合使用,一般會使用 STATEMENT 模式保存 binlog,對於 STATEMENT 模式無法複製的操作使用 ROW 模式保存 binlog,MySQL 會根據執行的 SQL 語句選擇寫入模式

文件結構

MySQL 的 binlog 文件中記錄的是對數據庫的各種修改操作,用來表示修改操作的數據結構是 Log Event。不同的修改操作對應的不同的 Log Event。比較常用的 Log Event 有:Query Event、ROW Event、Xid Event等。binlog文件的內容就是各種 Log Event 的集合

寫入機制

  • 根據記錄模式和操作觸發 event 事件生成 log event (事件觸發執行機制)
  • 將事務執行過程中產生 log event 寫入緩衝區,每個事務線程都有一個緩衝區
    Log Event 保存在一個 binlog_cache_mngr 數據結構中,在該結構中有兩個緩衝區,一個是 stmt_cache,用於存放不支持事務的信息;另一個是 trx_cache,用於存放支持事務的信息
  • 事務在提交階段會將產生的 log event 寫入到外部 binlog 文件中
    不同事務以串行方式將 Log Event 寫入 binlog 文件中,所以一個事務包含的 Log Event 信息在 binlog 文件中是連續的,中間不會插入其他事務的 Log Event

和 Redo Log 區別

  • Redo Log 是屬於 InnoDB 引擎功能,Binlog 屬於 MySQL Server 自帶功能,並且是以二進制文件記錄
  • Redo Log 屬於物理日誌,記錄該數據頁更新狀態內容;Binlog 是邏輯日誌,記錄更新過程
  • Redo Log 日誌是循環寫,日誌空間大小固定;Binlog 是追加寫入,寫完一個寫下一個,不會覆蓋使用
  • Redo Log 作爲服務器異常宕機後事務數據自動恢復使用;Binlog 可以作爲主從複製和數據恢復使用。Binlog 沒有自動 crash-safe 能力
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章