Mysql补全计划(五)

前言

今天继续来完成补全计划!

InnoDB存储引擎

逻辑存储结构

  • 表空间

    表空间是InnoDB存储引擎逻辑的最高层,当用户启用了参数innodb_file——per_table,则每张表就会有一个表空间,一个mysql实例可以对应多个表空间,用于存储记录、索引。

  • 分为数据段、索引段、回滚段,InnoDB是索引组织表,数据段就是B+树的叶子结点。段可以用来管理多个区。

  • 每个区的大小为1M,默认情况下,InnoDB存储引擎页大小为16k,即一个区里面有64个连续的页。

  • 页是磁盘管理的最小单元,每个页大小默认为16kb

  • InnoDB存储数据是按照行进行存放的。

    在行中,默认有两个隐藏字段:

    • Trx_id:每次对某条记录进行改动时,都会把对应的事务id赋值给trx_id隐藏列
    • Roll_pointer:每次对某条记录进行改动时,都会把旧的版本写入undo日志,然后这个隐藏列就相当于一个指针,可以通过它来找到该记录修改前的信息,也就是可以用这个指针来进行undo日志的操作。

内存结构

主要分为四大块,Buffer Pool、Change buffer、Adaptive Hash Index、Log Buffer

Buffer Pool

缓存池的思想我们已经很熟悉了,如果每次操作都是进行磁盘IO,那么效率会很慢,,我们可以把经常使用的数据加载到缓存池里面,这样就可以避免每次访问的时候都进行磁盘IO

在执行增删改查操作时,先操作缓存池中的数据,然后再以一定的频率刷新到磁盘中,从而减少磁盘IO,加快处理速度。

Change Buffer

这个其实也是缓存而已,只不过是专门对于二级索引建立的,在修改二级索引有关的数据时,会把修改的遍历提交到这个缓存中,等待某个时机后会与buffer pool合并刷新到磁盘中。

自适应hash

自适应hash索引,用于优化对Buffer Pool数据的查询。

hash索引在进行等值匹配时,一般性能是要高于B+,但是hash索引又不适合做范围查询、模糊匹配等。

自适应hash可以理解为是InnoDB存储引擎对热点数据、热点页加快访问而自动创建管理的索引。

Log Buffer

日志缓冲区,用来保存需要写入到磁盘中的日志数据。像redo log等。在进行进行修改或删除语句时,mysql会开启自动事务提交。在提交时,为了保证事务的一致性和完整性,我们通常会使用redo log,即将事务写道log buffer中,等待一定时机把这个事务刷新到磁盘里面,这样,就算mysql崩溃了,也会去重新加载磁盘上的redo log,然后更新事务。

🙅‍自动事务提交不支持事务回滚(RollBack)

事务什么时候提交到磁盘有三种选择:

  • 每次事务提交的时候写入缓存并立即刷新到磁盘中,是默认选项
  • 每秒将日志刷新到磁盘中
  • 日志提交到缓存中后,每秒刷新日志到磁盘中。

磁盘结构

image-20250620093407461

后台线程

主要包括

  • Master Thread
  • IO Thread
  • Purge Thread
  • Page Cleaner Thread

Master Thread

核心后台线程,负责调度其它线程,还负责将缓冲池中的数据异步刷新到磁盘中。

IO Thread

image-20250620093931699

可以通过下面这条指令来查看InnoDB的状态信息

1
 show engine innodb status \D;

Purge(净化) Thread

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

Page Cleaner Thread

协助Master Thread刷新脏页到磁盘的线程,减轻Master Thread的压力。

事务

事务四大特性

原子性

要么全部成功提交,要么全部失败,不存在中间状态。

一致性

事务完成时,所有数据必须保证一致性。

隔离性

多个事务之间互不影响,而且事务在不受外界影响的隔离环境中运行。

持久性

事务一旦提交或者回滚,它对数据库中的数据改变就是永久的。

对于这四大特性,由实现原理分为两部分,其中原子性、一致性、持久性是通过redo logundo log保障的。

隔离性是通过数据库的锁和MVCC保证的。

image-20250620095105250

Log日志

最重要的就是redo logundo log,下面来详细说一下啊这些日志。

Redo log和WAL预写日志

Redo log主要分为两类,一种是存在于磁盘上,另一种就是存在于内存中。

内存中主要是重做缓存的部分,也就是log buffer缓存,当事务提交时,就会把所有修改信息全部保存在缓存中,然后根据一定的刷新时机,把所有信息全部存储在该日志文件中。

可以重载恢复的一部分是存在于磁盘上,从而做到了持久化恢复,当发生错误时,就可以进行数据恢复。

记住,redo log是帮助Mysql服务器崩溃重启而建立的,当数据库崩溃时,再次重启就可以从磁盘中的redo log日志中加载未提交恢复的事务。

在业务操作中,我们操作数据一般是随机读写磁盘的,而不是顺序读写磁盘的。而redo log在往磁盘中写入文件,由于是日志文件,都是顺序写的。顺序写的效率要远大于随机写。这种先写日志的方式,叫做WAL

其实上面这段话有些绕,这种方式也算是一种独特的持久化策略。因为每次修改一个数据、删除数据基本上都是随机的,此时,每次都写一个数据实际上就会造成一次随机读取。而对于redo log日志来说,先写入内存中,然后再刷新到磁盘中,对于磁盘中的日志文件来说,就是**(顺序读写的(连续追加**)

Undo log

事务的一个关键特点就是支持回滚

当事务进行回滚时,之前对数据的修改“都不算话”!

其实现思路是反着来,例如,当执行一条insert语句时,就在undo log中保存一条相反语句即可,属于是逻辑日志

多版本并发控制MVCC

我觉得MVCC就是为各种事务的隔离级别所设置的。实际上,MVCC把各种读写分为多个版本,从而做到可重复读。

当前读

当前读读的是最新的版本,读取时还要确保其它并发事务不能修改当前记录,下面这些操作都是一种当前读

  • 1
    
    select ... lock in share mode; -- 共享锁
    
  • 1
    2
    3
    
    update 
    insert
    delete -- 排他锁
    

    都是一种当前读。

快照读

简单的select 就是快照读,快照读的意思就是在某个时候对数据进行一个“拍照”,记录下那个时候的数据。

还记得我们的几种事务隔离级别吗?他们解决什么问题来着?

RC,读提交,解决的是“一个线程可以读到另一个线程未提交的事务”的问题,对应问题是脏读

RR,可重复读,解决的是“一个线程前后读到的数据不一致的情况”,对应问题是不可重复读

序列化,解决的是“一个线程之前可以读到数据,之后发现读不了这个数据”的问题,对应的问题是幻读

对于Rc隔离级别,一个线程只可以读到另一个线程已经提交过的事务,所以,当另一个线程的事务未提交时,每次select的时候都生成一个快照读。

对于RR的隔离级别,实际上,为了解决可重复读,一个很容易想到的思路是,在第一次对某个数据进行查询操作的时候,就生成这个数据的快照,这样,每次都去读这个数据的快照,也就解决了“不可重复读”的问题。

对于”序列化“的隔离级别来说,快照读会变为当前读,总是保证事务提交后再去读,也就是说,序列化读到的数据永远都是最正确的,也就解决了幻读。

MVCC

MVCC的含义就是去维护一份数据的多个版本,从而避免了在并发场景下同一份数据被争抢而导致的频繁lock/unlock操作。

下面我们来分析一下InnoDB表中的一些个隐藏字段

隐藏字段

在创建表时,除了我们手动创建的字段类型外,还有一些辅助字段。

image-20250620210722727

undo log

前面已经介绍了一些undo log的基本思想,实际上,当事务成功提交后,所产生的undo log就会被删除。

下面我们来看一看上面这些隐藏字段和undo log是怎么做到事务回滚的吧!

假设有一张表为

image-20250620211530107

在刚开始,这个表的某行数据还没有被修改过,所以也就没有需要回滚的版本。

假设有多个事务在并发执行,我们假设为事务1 事务2 事务3 事务4,当事务1进行数据提交时,我们需要修改三个地方

  • 生成对应的undo log日志,以免日后数据回滚
  • 修改DB_TRX_ID字段,把这个值改为1,意思是”事务id为1的事务修改了这行数据
  • 修改当前数据的DB_ROLL_PTR数据字段,把当前字段指向undo log

同样的,这几个事务在并发执行的时候,也就会轮流修改这些数据,由于事务还未提交,对应的undo log日志也会一直存在,从而变成一个undo log日志链

image-20250620212214150

总结

下次来讨论一下视图以及MVCC实现的一些具体原理。

Licensed under CC BY-NC-SA 4.0
花有重开日,人无再少年
使用 Hugo 构建
主题 StackJimmy 设计