十、MySQL InnoDB引擎底层解析

一、InnoDB记录的存储结构和索引页结构

1、InnoDB的三大特性

双写机制、Buffer Pool、自适应Hash索引。

自适应Hash索引:

  • 针对热点数据创建hash索引,下一次查询的sql命中这些数据时,可以直接返回
  • 是innoDB底层自动维护的索引,无法手动干预

2、引入需求

客户端、表存储在哪里?

表中数据是什么格式?

以什么方式去访问这些数据?

事务和锁的原理?

3、如何从磁盘中读取数据

数据分成页(磁盘 -> 内存),每页16KB;

4、行格式

对于开发人员来说,以条数为单位,磁盘中存放一条数据叫行格式,有以下几类

  1. COMPACT;
  2. Redundant;
  3. Dynamic; // 默认
  4. Compressed;

查询行格式

java 复制代码
show variables like 'innodb_default_row_format';

5、行格式结构 & 说明

以COMPACT展示:

|-----------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 部分 | 说明 |
| 变长字段长度列表 | 所有【变长的字段】的长度组成的列表; varchar数据类型; 最大的数据长度 <= 255字节,就用1个字节记录,大于就用2个字节记录 |
| NULL值列表 | 存储当前行【所有字段】是否为null,用0和1表示; * 1 - null * 0 - 非null |
| 记录头信息 | 5个字节构成,按二进制的位数存储,40位; _____________________________________________________________________ | 头信息 | 二进制位数 | 解释 | 预留位1 1 没有使用; 预留位2 1 没有使用; delete_mask 1 标记是否被删除; min_rec_mask 1 B+树的每层非叶子节点中的最小记录都会添加该标记; n_owned 4 表示当前记录拥有的记录数; heap_no 13 表示当前记录的类型:0-普通记录,1-B+树非叶子节点记录,2-表示最小记录,3- 表示最大记录; next_record 16 表示下一条记录的相对位置; |
| 隐藏信息--DB_ROW_ID | 如果没设置表的主键列,那就自动创建这一列做主键; |
| 隐藏信息--DB_TRX_ID | 事务的id |
| 隐藏信息--DB_ROLL_PTR | 回滚指针 |

6、数据溢出

定义:一个字段的大小(varchar 60000字节)就大于一页(16KB = 16384字节)

处理方法:

|------------------------------|-----------------------------------------------------------|
| 行格式 | 处理方法 |
| COMPACT; Redundant; | 化整为零法; 当前页只保存【所有字段】的前768个字节的数据; 剩余的数据存到其他页中,留出20个字节指向其他页; |
| Dynamic; Compressed; | 别处寄存法; 如果数据长度超过768个字节,那么当前页什么都不保存; 只利用20字节指向其他页; |

7、索引页格式

聚簇索引:关联了数据,可以理解成数据页。

二、表空间

表空间是抽象概念,是数据目录下的一个文件(ibdata1)。

系统表空间

  • MySQL只有一个 系统表空间,用来保存额外的数据 ,和系统数据

  • InnoDB数据字典: 以B+树保存表信息;
    独立表空间:

  • 行在页中,有若干条; //可能一个页放多条数据,可能多个页才放得下一条数据;

  • 页在区中,有64页,每个页是物理连续的,消除大量随机 I/O

  • 区在组中,有256个区;

  • 组在段中,有若干个组,是个逻辑的分组;// 应对范围查询

  • 段在表空间中,有页节点段,非页节点段,回滚段;

三、InnoDB的双写缓冲区/双写机制(doublewrite buffer)

|--------|-------------------------------------------------------------------------------------------------------------------------------------------|
| | 说明 |
| 目的 | innodb按页(16KB)做单位,操作系统按4KB做单位; 防止一个页写入了4KB后断电,出现页缺失情况; |
| 作用 | 保证数据写入的可靠性,用来防止丢失数据; |
| 组成 | 双写缓冲区在系统表空间中; 包含两个区,分别是:extent1区(1MB)和extent2区(1MB)这两个区的物理地址是连续的,写入的快; 共128个页,页的编号 64 ~ 192,第一个区和第二个区; |
| 机制 | 把数据页写入数据文件前,先向双写缓冲区的两个区写入数据,两个区都写入完成后,才会再将数据页写入数据文件的合适位置; 写2次磁盘,第一次写双写缓冲区(磁盘),第二次真正写入数据文件(磁盘); 如果在写页的时候出现意外,可以在双写缓冲区找到备份完成恢复; |
| 理解 | 先把要写入的数据快速的保存到一个磁盘,再慢慢分发到正确的位置; 相当于一个事务,一个页中的数据要么都插入,要么都失败; |

四、InnoDB的Buffer Pool

1、定义

MySQL服务启动时,申请一块连续内存用来快速操作数据,这块连续内存叫缓冲池(buffer pool);

访问MySQL数据时,如果在磁盘直接访问,会很慢。所以把数据以页为单位磁盘加载到内存中;
访问磁盘中的数据页A时,A从磁盘加载到buffer Pool,再做操作;

如果已经加载了,直接操作;

2、结构

缓存页存放的是某个操作命中 的MySQL在磁盘中的数据

每个缓存页都有一个它自己的控制块;

控制块:占用5%左右的空间,存放控制相关信息;指示当前数据页的区号、页号、在缓冲池中的位置等信息;

数据页的编号 = A的表空间号 + A的页号;

3、free链表

空闲链表,一个双向列表。

|----------|----------------------------------------------------------------------------------------------------------------------|
| 作用 | 管理缓存页的分配; 控制buffer pool的时候可以从这个列表入手; 只要拿到一个控制块,那么它指向的缓冲页就拿到了; |
| 结构 | 由许多控制块组成,可以理解成一个List; ​List包括:start,end,count = n,控制块;​ start -> 控制块1 <-> 控制块2 <-> end;​ |
| 初始化 | 在初始化缓冲池的时候创建free List,里面的每个节点都是空的; |
| 使用过程 | 在数据页进入buffer pool的时候,找到其中的一个控制块,拿到这个控制块指向的缓存页; * 向控制块写入控制信息; * 向缓存页存储读取的数据; List中的控制块被写入了数据,就认为该缓存空间被使用,把控制块移除出空闲链表; |

4、脏页

背景: 数据在做修改时,要先修改内存buffer pool,后写磁盘;

问题: 如果只来得及修改内存,还没有写磁盘,这时称buffer pool中的页为"脏页"

5、flush链表(刷新链表)

|--------|---------------------------------------------|
| 作用 | 针对数据修改,存储脏页,批量地把脏页写入磁盘; |
| 背景 | 数据在做修改时,会出现脏页; |
| 机制 | 内存中buffer pool的数据页被修改后,不立即写入到磁盘,而是攒一波后批量提交; |

​6**、LRU链表(最少使用淘汰链表)**

|----------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 结构​ | 与free链表结构相似; free链表移除出来的控制块,放到LRU链表的第一格; |
| 作用 | 针对缓存淘汰控制,最少使用原则; 只保留最近的数据页,移除最早的数据页; |
| 机制 ​-- 淘汰步骤: | 1、buffer pool中没有数据页时,把数据页A保存到LRU的第一个控制块上; 2、数据页B 进来时,把B 放到第一个控制块上,把A往后挪一格; 3、数据页C 进来时,AB都往后挪一格; 4、再次命中A 时,A 放到第一格,BC都往后挪一格; 5、如果空间不够,那就淘汰最后一格的数据页; |

7、LRU淘汰算法遇到的问题

InnoDB中的预读:

执行SQL时,查询优化器会预先加载可能用到的数据页到buffer pool;

  • 随机预读:在读取某个区中的数据页时,如果连续读取了13个以上的页,使用异步线程预读取当前区的所有页;
sql 复制代码
-- 获取随机预读开关状态,默认关闭
show variables like 'innodb_random_read_ahead';
  • 线性读取:在读取某个区中的数据页时,如果连续读取了56个以上的页,MySQL会自动启动一个异步线程,提前读取下一个区中的所有页;
sql 复制代码
-- 获取线性预读阈值
show variables like 'innodb_read_ahead_threshold'; 

在预读数据后,会大量的将已有缓存页顶没了;

SQL语句触发全表扫描:

进行表关联,一张表可能扫描多次;

每次扫描都会淘汰一次LRU链表;

8、LRU的链表的优化

InnoDB把LRU链表分成了两段:

  • young区域(热数据 63%)
  • old区域(冷数据 37%)
sql 复制代码
-- 查询划分规则:
show variables like 'innodb_old_blocks_pct'; 

|--------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 对预读的优化 | InnoDB规定数据页初次从磁盘加载(只是加载,没有读取)到buffer pool中,该缓冲页对应的控制块会放到old区域的头部;这样预读页就只会在old区域,不会进入young区域; |
| 对全表扫描的优化 | 虽然首次加载的数据页放到old区域的头部,但是全表扫描涉及到了数据页的读取,第一次访问的时候就会将数据页放到young区域; 所以InnoDB规定:对old区域的缓冲页进行第一次访问时,在它的控制块中写入访问的时间;如果后续的访问时间与第一次的访问时间超出了1s(1000ms),才将数据页放到young区域; |
| 总结 | InnoDB规定了两件事: * 新来的数据页要放在old区域 * 第一次读取old区域的页要记录时间; |

五、Buffer Pool的flush链表的管理

待补充

六、Buffer Pool的LRU链表的管理

待补充

相关推荐
重庆小透明1 小时前
【从零开始学习JVM | 第六篇】运行时数据区
java·jvm·后端·学习
PP东1 小时前
JDK8新特性之Steam流
java
能工智人小辰2 小时前
二刷苍穹外卖 day02
java
Estar.Lee2 小时前
MySQL中外键约束详解 外键在表关系维护中的作用
运维·数据库·mysql·api·免费api
灯琰12 小时前
五款MySQL 可视化客户端软件
数据库·mysql
两袖清风9982 小时前
【MySQL】三大范式
数据库·mysql
bxlj_jcj4 小时前
深入剖析Debezium:CDC领域的“数据魔法棒”
java·架构
Wooden-Flute4 小时前
八、数据库恢复技术
服务器·数据库·oracle
南棱笑笑生4 小时前
20250611在全志H3平台的Nano Pi NEO CORE开发板上运行Ubuntu Core16.04时让4G模块EC200A-CN使用AT命令拨号上网
linux·数据库·ubuntu
叶 落4 小时前
ubuntu 安装 JDK8
java·ubuntu·jdk·安装·java8