MySQL 篇-深入了解 InnoDB 引擎的逻辑存储结构、架构、事务原理、MVCC 原理分析(RC 级别、RR 级别)
需要注意的是,此时修改之后的数据页在缓冲区中,是根据一定的时间才将脏数据页刷新到磁盘中,而不是数据修改完之后马上刷新的,也不是提交事务完之后马上提交的。2)Change Buffer:更改缓冲区(针对于非唯一的二级索引页),在执行 MDL 语句时,如果这些数据 Page 没有在 Buffer Pool 中,不会直接操作磁盘,而会将数据变更存放到更改缓冲区 Change Buffer 中,在未来数据
🔥博客主页: 【小扳_-CSDN博客】
❤感谢大家点赞👍收藏⭐评论✍
文章目录
1.0 InnoDB 引擎 - 逻辑存储结构
逻辑存储结构如图所示:
1)表空间(ibd 文件):一个 MySQL 实例可以对应多个表空间,用于存储记录、索引、表结构等数据。
2)段:分为数据段、索引段、回滚段,InnoDB 是索引组织表,数据段就是 B+ 树的叶子节点,索引段即为 B+ 树的非叶子节点。段用来管理多个区。
3)区:表空间的单元结构,每个区的大小为 1M 。默认情况下,InnoDB 存储引擎页大小为 16 kb,即一个区中共有 64 哥连续的页。
4)页:是 InnoDB 存储引擎磁盘管理的最小单元,每个页的大小默认为 16 kb,为了保证页的连续性,InnoDB 存储引擎每次从磁盘申请 4-5 个区。
5)行,InnoDB 存储引擎数据是按行进行存放的。
6)Trx_id:每次对某条记录进行改动时,都会把对应的事务 id 赋值给 trx_id 隐藏列。
7)Roll_pointer:每次对某条记录进行改动时,都会把旧的版本写入到 undo 日志中,然后这个隐藏列就相当于一个指针,可以通过它来找到该记录修改前的信息。
2.0 InnoDB 引擎 - 架构
MySQL 5.5 版本开始,默认使用 InnoDB 存储引擎,它擅长事务处理,具有崩溃恢复特性,在日常开发中使用非常广泛。
下面时 InnoDB 架构图,左侧为内存结构,右侧为磁盘结构:
2.1 InnoDB 引擎 - 内存结构
1)Buffer Pool:缓冲区是主内存中的一个区域,里面可以缓存磁盘上经常操作的真实数据,在执行增删改查操作时,先操作缓冲区中的数据,若缓冲区没有目标数据,则从磁盘加载并缓存,然后再以一定频率刷新到磁盘,从而减少磁盘 I/O,加快处理速度。
缓冲池以 Page 页为单位,底层采用链表数据结构管理 Page,根据状态,将 Page 分为三种类型:
- free page:空闲 page,未被使用。
- clean page:被使用 page,数据没有被修改过。
- dirty page:脏页,被使用 page,数据被修改过,页中数据与磁盘数据产生不一致,因此,缓冲池会以一定的频率将数据刷新到磁盘中,以保证数据一致性。
2)Change Buffer:更改缓冲区(针对于非唯一的二级索引页),在执行 MDL 语句时,如果这些数据 Page 没有在 Buffer Pool 中,不会直接操作磁盘,而会将数据变更存放到更改缓冲区 Change Buffer 中,在未来数据被读取时,再将数据合并恢复到 Buffer Pool 中,再将合并后的数据刷新到磁盘中。
Change Buffer 的意义是什么?
与聚集索引不同,二级索引通常是非唯一,并且以相对随机的顺序插入二级索引。同样,删除和更新可能会影响索引树种不相邻的二级索引页,如果每一次都操作磁盘,会造成大量的磁盘 IO,有了 Change Buffer 之后,就可以在缓冲池中进行合并处理,减少磁盘 IO 。
3)Adaptive Hash Index:自适应 hash 索引,用于优化对 Buffer Pool 数据的查询。InnoDB 存储引擎会监控对表上各索引页的查询,如果观察到 hash 索引可以提升速度,则建立 hash 索引,称之为自适应 hash 索引。
自适应哈希索引,无需人工干预,是系统根据情况自动完成。
4)Log Buffer:日志缓冲区,用来保存要写入到磁盘中的 log 日志数据(redo log、undo log),默认大小为 16 MB,日志缓冲区的日志会定期刷新到磁盘中。如果需要更新、插入或删除许多行的事务,增加日志缓冲区的大小可以节省磁盘 IO。
相关的参数:
- innodb_log_buffer_size(缓冲区大小)
- innodb_flush_log_trx_commit(日志刷新到磁盘时机):设置为 1,代表日志在每次事务提交时写入并刷新到磁盘;设置为 0,每秒将日志写入并每秒刷新到磁盘一次;设置为 2,日志在每次事务提交后写入,并每秒刷新到磁盘一次。
2.2 InnoDB 引擎 - 磁盘结构
1)undo Tablespaces:撤销表空间,MySQL 实例在初始化时会自动创建两个默认的 undo 表空间(初始大小 16 M),用于存储 undo log 日志。
2)Temporary Tablespaces:InnoDB 使用会话临时表空间和全局临时表空间。存储用户创建的临时表等数据。
3)Doublewrite Buffer Files:双写缓冲区,InnoDB 引擎将数据页从 Buffer Poll 刷新到磁盘前,先将数据页写入双写缓冲区文件中,便于系统异常时恢复数据。
4)Redo log:重做日志,是用来实现事务的持久性,该日志文件由两部分组成:重做日志缓冲(redo log buffer)以及重做日志文件(redo log),前者是在内存中,后者在磁盘中。当事务提交之后会把所有修改信息都会存到该日志中,用于在刷新脏页到磁盘时,发生错误时,进行数据恢复使用。
以循环方式写入重做日志文件,涉及两个文件:ib_logfile0、ib_logfile1 。
2.3 InnoDB 引擎 - 后台线程
将数据从缓冲区写入到磁盘中,会用到后台线程:
1)Master Thread:核心后台线程,负责调度其他线程,还负责缓冲池中的数据异步刷新到磁盘中,保持数据的一致性,还包括脏页的刷新、合并插入缓存、undo 页的回收。
2)IO Thread:在 InnoDB 存储引擎中大量使用了 AIO 来处理 IO 请求,这样可以极大地提高数据库的性能,而 IO Thread 主要负责这些 IO 请求的回调。
3)Purge Thread:主要用于回收事务已经提交了的 undo log,在事务提交之后,undo log 可能不用了,就用它来回收。
4)Page Cleaner Thead:协助 Master Thread 刷新脏页到磁盘的线程,它可以减轻 Master Thread 的工作压力,减少阻塞。
3.0 InnoDB 引擎 - 事务原理概述
事务是一组操作的集合,它是一个不可分割的工作单位,事务会把所有的操作作为一个整体一起向系统提交或撤销操作请求,即这些操作要么同时成功,要么同时失败。
特性:
1)原子性:事务是不可分割的最小操作单元,要么全部成功,要么全部失败。由 undolog 来具体实现原子性。
2)一致性:事务完成时,必须使所有的数据都保持一致状态。由 redolog 和 undolog 来实现一致性。
3)隔离性:数据库系统提供的隔离机制,保证事务在不受外部并发影响的独立环境下运行。由 MVCC + 锁实现隔离性。
4)持久性:事务一旦提交或回滚,它对数据库中的数据的改变就是永久的。由 redolog 来实现持久性。
3.1 InnoDB 引擎 - redolog
redolog 重做日志,记录的是事务提交时数据页的物理修改,是用来实现事务的持久性。
该日志文件由两部分组成:重做日志缓冲以及重做日志文件,前者是在内存中,后者在磁盘中,当事务提交之后会把所有修改信息都存到该日志文件中,用户在刷新脏页到磁盘,发生错误时,进行数据恢复使用。
具体流程:
当客户端将该事务提交之后,首先在内存结构中的缓冲区查找是否存在目的数据,如果没有,则回到磁盘中查找且将该数据缓冲到内存中。
如果可以直接在内存中找到该数据,则对该数据在缓冲区中直接修改,此时该数据页就变成了脏数据页。
数据修改完成之后,重做日志缓冲也会将修改之后的数据进行记录起来且将该重做日志缓冲通过后台线程发送到磁盘中的重做日志文件中。需要注意的是,此时修改之后的数据页在缓冲区中,是根据一定的时间才将脏数据页刷新到磁盘中,而不是数据修改完之后马上刷新的,也不是提交事务完之后马上提交的。但是对于日志来说,通过设置参数,当事务提交之后,就可以将内存中的重做日志缓冲刷新到磁盘中的重做日志文件中。
当脏数据页刷新到磁盘过程中,可以正常的刷新到 ibd 文件中,则重做日志文件中的数据也就没有用处了,就会被循环刷新掉,而从释放存储空间。
而当脏数据页需要刷新到磁盘过程中,出现了错误,不能正常的刷新到磁盘中,这就可以通过重做日志文件进行恢复。恢复完之后,重做日志文件也会被循环刷新掉,而从释放存储空间。
相关概念:
1)事务提交与数据查找:当客户端提交事务后,系统首先在内存的缓冲区中查找目标数据。如果未找到,则会从磁盘加载数据到内存。这是典型的数据库操作流程。
2)脏数据页的概念:如果在内存中找到了数据并进行了修改,那么这个数据页就会被标记为“脏数据页”。脏数据页是指已经被修改但尚未写回磁盘的数据页。
3)重做日志的记录:在数据修改后,系统会将修改后的数据记录到重做日志缓冲区,并通过后台线程将这些日志写入磁盘的重做日志文件。这确保了在出现故障时可以通过重做日志恢复数据。
4)脏数据页的刷新策略:脏数据页并不会在每次修改后立即刷新到磁盘,而是根据一定的策略(如时间、内存压力等)进行批量刷新。这种延迟刷新可以提高性能。
5)日志的刷新策略:对于重做日志,通常可以通过配置参数在事务提交后立即将内存中的重做日志刷新到磁盘,以确保日志的持久性。
6)错误处理与恢复:如果在将脏数据页刷新到磁盘过程中出现错误,重做日志可以用来恢复数据。这是事务管理中重要的一部分,确保数据的一致性和持久性。
7)重做日志的循环刷新:无论是成功刷新脏数据页到磁盘,还是通过重做日志进行恢复后,重做日志文件中的数据都会被循环刷新(即定期清理或覆盖),以释放存储空间。
3.2 InnoDB 引擎 - undolog
回滚日志,用于记录数据被修改前信息,作用包含两个:提供回滚和 MVCC(多版本并发控制)。
undolog 和 redolog 记录物理日志不一样,undolog 是逻辑日志。可以认为当 delete 一条记录时,undo log 中会记录一条对应的 insert 记录,反之亦然,当 update 一条记录时,它记录一条对应相反的 update 记录。当执行 rollback 时,就可以从 undo log 中的逻辑记录读取到相应的内容并进行回滚。
undolog 销毁:undolog 在事务执行时产生,事务提交时,并不会立即删除 undolog,因为这些日志可能还用于 MVCC 。
undolog 存储:undolog 采用段的方式进行管理和记录,存放在前面介绍的 rollback segment 回滚段中,内部包含 1024 个 undolog segment 。
4.0 InnoDB 引擎 - MVCC 基本概念
MVCC(多版本并发控制,Multi-Version Concurrency Control)是一种用于数据库管理系统中的并发控制技术。它允许多个事务同时访问数据库。
MVCC 的基本思想是为每一个事务提供数据的多个版本。每当数据被修改时,系统不会直接覆盖原有的数据,而是创建一个新的数据版本,同时保留旧的数据版本。这样,正在读取旧版本数据的事务不受影响,可以继续操作,而新事务则可以看到最新的数据版本。
简单来说,MVCC 是指维护一个数据的多个版本,使得读写操作没有冲突,快照读为 MySQL 实现 MVCC 提供一个非阻塞读功能。
MVCC 的基本概念:
1)当前读:读取的是记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁。对于日常操作:select ... lock in share mode(共享锁)、select ... for update、update、insert、delete(排他锁)都是一种当前读。
2)快照读:简单的 select(不加锁)就是快照读,快照读,读取的是记录数据的可见版本,有可能是历史数据,不加锁,是非阻塞读。
- Read Committed:每次 select,都生成一个快照读。
- Repeatable Read:开启事务后第一个 select 语句才是快照读的地方。
- Serializable:快照读会退化为当前读。
4.1 MVCC - 隐藏字段
记录中隐藏的字段:
1)DB_TRX_ID:最近修改事务 ID,记录插入这条记录或最后一次修改该记录的事务 ID。
2)DB_ROLL_PTR:回滚指针,指向这条记录的上一个版本,用于配合 undolog,指向上一个版本。
3)DB_ROW_ID:隐藏主键,如果表结构没有指定主键,将会生成该隐藏字段。
4.2 MVCC - undolog 版本链
undolog:
回滚日志,在 insert、update、delete 的时候产生的便于数据回滚的日志。
当 insert 的时候,产生的 undolog 日志只在回滚时需要,在事务提交后,可被立即删除。而 update、delete 的时候,产生的 undolog 日志不仅在回滚时需要,在快照读时也需要,不会立即被删除。
undolog 版本链:
不同事务或相同事务对同一条记录进行修改,会导致该记录的 undolog 生成一条记录版本链表,链表的头部是最新的旧记录,链表尾部是最早的旧记录。
生成 undolog 版本链具体过程:
1)当事务 1 插入一条数据,提交完成之后,先会存放到内存缓冲区中:
DB_TRX_ID = 1,这是因为操作的事务的 ID 为 1。DB_ROLL_PTR = null,这是因为该数据是最新插入的数据,没有事务对其进行操作过。
2)当事务 2 对该数据进行修改操作:
首先,undolog 会对修改之前的数据进行保存起来,再将数据进行修改:
其中 DB_TRX_ID 修改为事务 2 的 ID,DB_ROLL_PTR 指向之前的数据的地址,便于回滚或者便于每一个事务可以获取到该事务所对应的数据版本。
3)当事务 3 也对该数据进行修改操作:
同理,将修改之前的数据存放到 undolog 中,再来修改数据:
将 DB_TRX_ID 修改为当前事务 ID 为 3,DB_ROLL_PTR 指针指向前一个数据的地址。
4)当事务 4 也对该数据进行修改:
同理,将未修改的数据先存到 undolog 中,再来修改数据:
将 DB_TRX_ID 改为当前操作的事务 ID,DB_ROLL_PTR 指向前一个数据的地址。
4.3 MVCC - readview 介绍
ReadView 读视图是快照读 SQL 执行时 MVCC 提取数据的依据,记录并维护系统当前活跃的事务(未提交的)id 。
ReadView 中包含了四个核心字段:
1)m_ids:当前活跃的事务 ID 集合。
2)min_trx_id:最小活跃事务 ID 。
3)max_trx_id:预分配事务 ID,当前最大事务 ID + 1 。
4)creator_trx_id:ReadView 创建者的事务 ID 。
版本链数据访问规则:
1)trx_id == creator_trx_id ?如果满足条件,则说明当前版本数据就是当前事务更改的可以访问的到。
2)trx_id < min_trx_id ?如果满足条件,则说明数据已经提交了。可以访问得到该版本数据。
3)trx_id > max_trx_id ? 如果满足条件,则说明该事务是在 ReadView 生成之后才开启的,不能访问该版本数据。
4)min_trx_id <= trx_id <= max_trx_id 且 trx_id 不在 m_ids 中 ? 如果满足条件,则可以访问该版本数据。
不同的隔离级别,生成 ReadView 的时机不同:
1)READ COMMITTED:在事务中每一次执行快照读时生成 ReadView 。
2)REPEATABLE READ:仅在事务中第一次执行快照读时,生成 ReadView,后续复用该 ReadView 。
4.4 MVCC - 原理分析(RC 级别)
READ COMMITTED:在事务中每一次执行快照读时生成 ReadView 。
举个例子:
接下来查看事务 5 查询所得到的数据版本:
首先,第一次快照的 ReadView:m_ids 集合中活跃事务有 3、4、5,事务 5 就是当前、min_trx_id 最小活跃事务 ID 为 3、max_trx_id 下一个事务 ID 为 6、creator_trx_id 创建该快照视图的事务为事务 5 。
接着,根据版本链数据访问规则:
之前所生成的版本数据链:
从最新的版本数据开始往下查找合适当前事务的版本数据。
当前最新的版本数据的 trx_id 为 4,不满足规则一中的 creator_id == trx_id,说明不是当前事务所创建的数据;不满足规则二,min_trx 为 3,比 3 还小,说明事务还未提交;不满足规则四,trx_id = 4 在 m_ids 集合中。
接着到下一个版本数据 trx_id = 3,不满足规则一,creator_id = 5,说明不是当前事务所创建的数据;不满足规则二,min_trx 为 3,而 trx_id = 3,说明事务还未提交;不满足规则四,trx_id 在 m_ids 集合中。
继续下一个版本数据 trx_id = 2,不满足规则一,creator_id = 5,说明不是当前事务所创建的数据;满足规则二, min_trx 为 3,而 trx_id = 2,trx_id < min_trx 成立,因此当前事务 5 中第一个查询数据的版本为 trx_id 为 2 事务的版本。
至于第二次快照所生成的 ReadView,结合 ReadView 和根据版本数据访问规则来进行查询,查询的流程是一样的。这里就不过多赘述了。
4.5 MVCC - 原理分析(RR 级别)
仅在事务中第一次执行快照读时,生成 ReadView,后续复用该 ReadView 。
这里只需要根据第一次执行的快照读所生成的 ReadView 结合版本数据访问规则来查询符合要求的版本数据,剩余的快照所得到的版本数据都是一样的。
4.6 小结
1)持久性(Durability):通过重做日志(Redo Log)来实现。当事务提交后,系统会将相关的修改记录写入重做日志。由于重做日志在磁盘上是持久的,即使系统崩溃,能够通过重做日志来恢复提交的事务,从而保证已提交事务的修改不会丢失。
2)原子性(Atomicity):通过撤销日志(Undo Log)来实现。原子性确保事务要么完全成功(提交),要么完全失败(回滚)。如果事务在执行过程中出现错误,可以使用撤销日志将已执行的修改回滚到事务开始前的状态,从而确保操作的原子性。
3)一致性(Consistency):通过重做日志和撤销日志的结合使用来实现。在事务执行过程中,不管是提交还是回滚,数据库都要确保状态保持一致。提交时应用重做日志,回滚时则使用撤销日志,从而在这两个过程中保证了数据的一致性。
4)隔离性(Isolation):通过多版本并发控制(MVCC)和锁机制来实现。MVCC 允许事务读取操作时获取一致的视图,而不必等待其他事务的完成,提高了并发性。同时,锁机制确保在写操作时,其他事务不能同时修改同一数据行,从而保证事务之间的隔离性。
更多推荐
所有评论(0)