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 能力
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260

推荐阅读更多精彩内容