InnoDB引擎

该笔记是基于b站视频学习制作:MySQL数据库入门到精通



1. 概念

意义:

  • InnoDB是MySQL的一种存储引擎,它是MySQL 8.0的默认存储引擎。InnoDB存储引擎的主要特点是支持事务处理、回滚、崩溃修复能力和多版本并发控制的事务安全。它也是MySQL上第一个提供外键约束的表引擎
  • InnoDB存储引擎的设计目标是在高可靠性和高性能之间取得平衡。它将数据划分为若干页,以页作为磁盘与内存交互的基本单位,一般页的大小为16KB。这样的设计可以减少内存与磁盘的交互次数,从而提升性能
  • 此外,InnoDB还支持ACID事务,即原子性、一致性、隔离性、持久性。这些特性使得InnoDB在处理事务方面具有优势
  • 总的来说,InnoDB存储引擎在MySQL中的应用非常广泛,它的存在使得MySQL能够更好地处理复杂的事务和保证数据的安全性。如果你想查看自己的数据库默认使用的存储引擎,可以通过使用命令SHOW VARIABLES LIKE 'storage_engine';

作用:

  • 事务处理:InnoDB支持ACID事务,即原子性、一致性、隔离性、持久性。这些特性使得InnoDB在处理事务方面具有优势
  • 数据完整性:InnoDB是MySQL上第一个提供外键约束的表引擎,这有助于保证数据的完整性和一致性
  • 并发控制:InnoDB使用多版本并发控制(MVCC)来处理并发操作,这可以提高数据库的并发处理能力
  • 崩溃恢复:InnoDB具有崩溃恢复能力,可以在系统崩溃后恢复数据,保证数据的安全性
  • 性能优化:InnoDB将数据划分为若干页,以页作为磁盘与内存交互的基本单位,一般页的大小为16KB。这样的设计可以减少内存与磁盘的交互次数,从而提升性能


2. 逻辑存储结构

image-20231202215131646
  • 表空间(ibd文件):一个MySQL实例可以对应多个表空间,用于存储记录、索引等数据
  • 段:分为数据段(Leaf node segment)、段索引(Non-leaf node segment)、回滚段(Rollback segment),InnoDB是索引组织表,数据段就是B+树的叶子节点,索引段即为B+树的非叶子节点。段用来管理多个Extent(区)
  • 区:表空间的单元结构,每个区的大小为1M。默认情况下,InnoDB存储引擎页大小为16K,即一个区中一共有64个连续的页
  • 页:是InnoDB存储引擎磁盘管理的最小单元,每个页的大小默认为16KB。为了保证页的连续性,InnoDB存储引擎每次从磁盘申请4-5个区
  • 行:InnoDB存储引擎数据是按行进行存放的
    • Trx_id:每次对某条记录进行改动时,都会把对应的事务id赋值为trx_id隐藏列
    • Roll_pointer:每次对某条引记录进行改动时,都会把旧的版本写入到undo日志中,然后这个隐藏列就相当于一个指针,可以通过它来找到该记录修改前的信息


3. 内存结构

MySQL 5.5版本开始就默认使用InnoDB作为存储引擎,它擅长事务处理,具有崩溃恢复特性。下面是InnoDB架构图,左侧为内存结构,右侧为磁盘结构

image-20231203140524143
  • Buffer Pool: 缓冲池是主内存中的一个区域,里面可以缓存磁盘上一些经常操作的真实数据,在执行增删改查操作时,先操作缓冲池中的数据,若缓冲池没有数据,则从磁盘中加载并缓存。然后再以一定频率刷新到磁盘,从而减少磁盘IO,加快处理速度
  • 缓冲池以Page页为单位,底层采用链表数据结构管理Page。根据状态,将Page分为三种类型:
    • free page:空闲page,未被使用
    • clean page:被使用page,数据没有被修改过
    • dirty page:脏页:没使用page,数据被修改过
  • Change Buffer:更改缓冲区(针对于非唯一二级索引页),在执行DML语句(增删改)时,如果这些数据Page没有在Buffer Pool中,则不会直接操作数据库,而是将数据变更存在Change Buffer中,在未来数据被读取的时候,再将数据合并到Buffer Pool中,任何再将合并后的数据刷新到磁盘中去
    • Change Buffer存在的意义:
      • 在InnoDB存储引擎中,change buffer的存在有着重要的意义。当索引字段内容发生更新时(例如update、insert、delete),如果对应的索引页在Buffer Pool中命中,就会直接更新缓存页。否则,InnoDB会将这些更新操作缓存在change buffer中,这样就无需从硬盘读入索引页。下次查询索引页时,会将索引页读入Buffer Pool,然后将change buffer中的操作应用到对应的缓存页,得到最新结果,这个过程称为merge。通过这种方式,可以保证数据逻辑的正确性。
      • 此外,change buffer还有助于减少硬盘随机IO读取和提高内存利用率,从而提升数据库的并发能力。为了防止异常宕机丢失缓存,当事务提交时,InnoDB会将change buffer记录的内容持久化到磁盘(redo log),等待时机更新磁盘的数据文件(刷脏)。因此,change buffer在内存中,如果万一MySQL实例挂了或宕机了,这次的更新操作不会全部丢失。最后,MySQL可以通过ibdata1或redolog恢复change buffer。
      • 总的来说,change buffer的存在可以提升索引性能,减少硬盘随机IO读取,提高内存利用率,以及保证数据逻辑
  • Adaptive Hash Index:自适应hash索引,用于优化Buffer Pool数据的查询。InnoDB存储引擎会监控对表上各索引页的查询,如果观察到hash索引可以提升速度,则建立hash索引,称之为自适应hash索引
  • Log Buffer:日志缓冲区,用来保存要写入到磁盘中的log日志数据(redo log、 undo log),默认大小为16MB,日志缓冲区的日志会定期刷新到磁盘中。如果需要更新、插入或者删除许多行的事务,增加日志缓冲区的大小可以节省磁盘I/O。有两个参数如下:
    • innodb_log_buffer_size:缓冲区大小
    • innodb_flush_log_at_trx_commit:日志刷新磁盘时机。有0,1,2三个值
      • 1:日志在每次事务提交时写入并刷新到磁盘(默认值)
      • 0:日志每次将日志写入并刷新到磁盘一次
      • 2:日志在每次事务提交后写入、并每秒刷新到磁盘一次


4. 磁盘结构

image-20231203140612885
  • System Tablespace:(系统表空间)是存储区域,用于存储doublewriter buffer(双写缓冲区)和change buffer(变更缓冲区)。如果用户创建的表在系统表空间中创建,而不是在文件每表或者通用表空间中创建,那么系统表空间也可能包含表和索引数据,在以前的MySQL版本中,系统表空间包含了InnoDB数据字典
  • File-Per-Table Tablespaces:(每表一个文件的表空间)是一种数据存储方式。当启用了innodb_file_per_table配置选项时,每个InnoDB表和它的索引会被单独存储在.ibd数据文件中。这个.ibd数据文件代表一个单独的表空间
  • General Tablespaces:(通用表空间)是一种可以存储多个表数据的共享表空间。它是使用CREATE TABLESPACE语法创建的
  • Undo Tablespaces:(撤销表空间)包含撤销日志,这些日志是包含有关如何撤销事务对聚簇索引记录的最新更改的信息的记录集合。MySQL实例在初始化时会自动创建两个默认的表空间(初始大小16M),用于存储undo log日志
  • Temporary Tablespaces:(临时表空间)是数据库中用于存储临时数据的空间
  • Doublewrite Buffer Files:(双写缓冲区)InnoDB引擎将数据页从Buffer Pool刷新到磁盘前,先将数据页写入双写缓冲区中,便于系统异常时恢复数据
  • Redo Log:(重做日志),是用来实现事务的持久性。该日志由两部分组成:重做日志缓冲(redo log buffer)和重做日志文件(redo log file),前者是在内存中,后者在磁盘中。当事务提交之后会把所有的修改信息存储在该日志中


5. 后台线程

Master Thread

核心后台线程,负责调度其他线程,还负责将缓冲池中的数据异步刷新到磁盘中去,保持数据的一致性,还包括赃页的刷新、合并插入缓存、undo页的回收


IO Thread

在InnoDB存储引擎中大量使用了AIO来处理IO请求,这样可以极大地提高数据库的性能,而IO Thread主要负责这些IO请求的回调


Purge Thread

主要用于回收事务已经提交了的undo log,在事务提交之后,undo log可能不用了,就用它来回收


Page Cleaner Thread

协助Master Thread刷新赃页到磁盘的线程,它可以减轻Master Thread的工资压力,减少阻塞



6. 事务原理

事务:事务是一组操作的集合,它是一个不可分割的工作单位,事务会把所有的操作作为一个整体一起向系统提交或者撤销操作请求,即这些操作要么同时成功,要么同时失败


特性:

  • 原子性(Atomicity):事务是不可分割的最小操作单元,要么全部成功,要么全部失败
  • 一致性(Consistency):事务完成时,必须使所有的数据都保持一致
  • 隔离性(Isolation):数据库系统提供的隔离机制,保证事务在不受外部并发操作影响的独立环境下运行
  • 持久性(Durability):事务一旦提交或者回滚,它对数据库中的数据的改变是永久的

redo log 和 undo log 保证了事务的原子性、一致性和持久性
锁和MVCC保证了隔离性


redo log

重做日志,记录的是事务提交时数据页的物理修改,是用来实现事务的持久性

该日志由两部分组成:重做日志缓冲(redo log buffer)和重做日志文件(redo log file),前者是在内存中,后者在磁盘中。当事务提交之后会把所有的修改信息存储在该日志中,用于在刷新赃页到磁盘,发生错误时,进行数据恢复

image-20231213202354900

undo log

回滚日志,用于记录数据被修改前的信息,作用主要有两个:提供回滚和MVCC

undo log是逻辑日志,它记录的是对数据的操作。当delete一条记录时,undo log中就会记录一条对应的insert记录,当update一条记录时,就会记录一条相反的update日志

undo log销毁:undo log在事务执行时产生,事务提交时,并不会立即删除undo log,因为这些日志可能还用于MVCC

undo log存储:undo log采用段的方式进行管理和记录



7. MVCC

MVCC,即多版本并发控制(Multi-Version Concurrency Control),是一种数据库管理系统中实现并发访问的方法。它允许在数据库中存在多个数据版本,从而使不同的事务能够同时访问同一数据的不同版本,提高了系统的并发性能。


当前读

当前读是指读取数据的最新版本,并且在读取的过程中对数据加锁,以防止其他事务同时修改相同的记录。这种读取方式常用于需要保证数据一致性的场景,如更新、删除、插入操作,以及使用select … for update(都是排他锁)或select … lock in share mode(共享锁)进行的查询


快照读

简单的select(不加锁)就是快照读,快照读读取的是记录数据的可见版本,有可能是历史数据,不加锁,是非阻塞读

  • Read Committed:每次select,都生成一个快照读
  • Repeatable Read:开启事务后第一个select语句才是快照读的地方
  • Serializable:快照读会退回为当前读

记录中的隐藏字段

隐藏字段 含义
DB_TRX_ID 最近修改事务ID,记录插入这条记录或者最后一次修改该记录的事务ID
DB_ROLL_PTR 回滚指针,指向这条记录的是一个版本,用于配合undo log,指向上一个版本
DB_ROW_ID 隐藏主键,如果表结构没有指定主键,将会生成该隐藏字段(如果有主键就不生成)

undo log

回滚日志,在insert、update、delete的时候产生的便于数据回滚的日志

当insert的时候,产生的undo log日志只在回滚时需要,在事务提交后,可被立即删除

而update、delete的时候,产生的undo log日志不仅在回滚时需要,在快照时也需要,不会立即被删除


undo log版本链

不同事务或相同事务对同一条记录进行修改,会导致该记录的undo log生成一条记录版本链表,链表的头部是最新的旧记录,链表尾部是最早的旧记录

当一个事务对数据进行修改时,它会在undo log中记录下修改前的数据版本,并更新数据记录的DB_TRX_IDDB_ROLL_PTR。如果另一个事务再次修改这条数据,它也会在undo log中记录下当前的数据版本,并更新DB_ROLL_PTR指向新的undo log。这样通过DB_ROLL_PTR指针串联起来的undo log就形成了一个版本链

这个版本链允许数据库在执行回滚操作时,根据事务的需要找到正确的数据版本,同时它也支持MVCC机制,使得不同的事务可以看到数据的不同历史版本,从而实现非阻塞的读取操作


ReadView

ReadView(读视图)是快照读SQL执行时MVCC提取数据的依据,记录并维护系统当前活跃的事务(为提交的)id

在数据库系统中,特别是在使用MVCC(多版本并发控制)机制的系统中,ReadView是用来实现不同隔离级别下的一致性读取的关键数据结构。ReadView的生成过程通常与事务的开始和查询操作紧密相关。


ReadView的生成过程

  • 事务开始:当一个事务开始时,数据库系统会为该事务分配一个唯一的事务ID(trx_id)
  • 活跃事务列表:系统会维护一个活跃事务列表,其中包含了所有尚未提交的事务的ID
  • 创建ReadView:在执行查询操作时,系统会创建一个ReadView
  • 确定数据版本可见性:ReadView会根据事务ID和数据版本的trx_id来确定哪些数据版本对当前事务是可见的

ReadView中包含了四个核心字段

字段 含义
m_ids 当前活跃的事务ID集合
min_trx_id 最小活跃事务ID
max_trx_id 预分配事务ID,最大活跃事务ID+1(事务ID是自增的)
creator_trx_id ReadView创建者的事务ID

版本链访问规则

trx_id表示的是当前被访问版本的ID

image-20231213223050767
  • 如果被访问版本的trx_idReadView中的creator_trx_id值相同,说明当前事务在访问自己修改过的记录,该版本可以被当前事务访问
  • 如果被访问版本的trx_id小于ReadView中的min_trx_id值,说明生成该版本的事务在当前事务生成ReadView前已经提交,该版本可以被当前事务访问
  • 如果被访问版本的trx_id大于或等于ReadView中的max_trx_id值,说明生成该版本的事务在当前事务生成ReadView后才开启,该版本不可以被当前事务访问
  • 如果被访问版本的trx_id值在ReadViewmin_trx_idmax_trx_id之间,就需要判断trx_id属性值是否在m_ids列表中。如果在,说明创建ReadView时生成该版本的事务还是活跃的,该版本不可以被访问。如果不在,说明创建ReadView时生成该版本的事务已经被提交,该版本可以被访问

不同的隔离级别,生成的ReadView的时机不同:

  • READ COMMITTED:在事务中每一次执行快照读时生成ReadView
  • REPEATABLE READ:仅在事务中第一次执行快照读时生成ReadView,后续复用该ReadView

隔离级别为RC下,ReadView的工作原理

RC(读已提交),它是指在这个隔离级别下,一个事务只能读取到其他事务已经提交的修改。这个级别的设计是为了防止脏读现象的发生

在RC隔离级别下,ReadView的工作原理是确保事务只能看到在该事务开始之前已经提交的更改。这是通过每次查询时都创建一个新的ReadView来实现的


隔离级别为RR下,ReadView的工作原理

RR(可重复读),它是指这个隔离级别下,一个事务在其执行期间可以多次读取同一数据集,并且每次读取的结果都是相同的,即使在这期间其他事务对这些数据进行了修改或更新。这种特性保证了数据的一致性,避免了在一个事务中出现不可重复读的问题

当一个事务在RR隔离级别下开始时,它会创建一个ReadView,这个ReadView相当于是一个数据快照。在整个事务执行期间,无论其他事务如何修改数据,当前事务都只能看到ReadView中的数据版本。这就意味着,即使其他事务提交了更新,当前事务仍然能够读取到它最初看到的数据版本,从而实现了可重复读的效果。

在RR隔离级别下,可以避免不可重复读和幻读,但可能会牺牲一些并发性能。这是因为为了维持数据的一致性,系统需要对数据加锁或使用其他机制来保证在事务执行期间数据不会被其他事务修改。这样的设计是为了在数据一致性和系统性能之间取得平衡。


InnoDB引擎
https://lzhengjy.github.io/2023/12/02/InnoDB引擎/
作者
Zheng
发布于
2023年12月2日
许可协议