前言
本来只是想了解下redo、undo log的机制,但发现好像牵扯挺多知识点,就写了这篇文章记录下。。。
InnoDB 架构
本文分析的mysql版本为8.0
一 InnoDB 内存结构
1.1 Buffer pool
Buffer pool(下文简称BP) 是在主内存中的一块区域,用于在访问时缓存表和索引数据。它可以直接从内存处理数据,因此处理速度非常快。
为了提高大容量读取操作的效率,BP被分成可以容纳多行的Page(默认16K)。BP的底层数据结构是链表,以此管理Page。
1.1.1 Buffer Pool LRU
BP的LRU是一种变体。BP插入数据时,采用了中间策略(midpoint insertion strategy)。中间策略将BP视为两个子列表:
1 | new sublist : 存放最近访问的子列表 |
LRU算法主要的操作如下:
- 默认将3/8的BP空间分配给old sublist
- 规定midpoint为
new sublist
的tail 与old sublist
的head交界处 - InnoDB读取Page到BP时,插入到midpoint(old sublist的头部)。Page被读取有两种情况:
- 用户的sql查询
- InnoDB的预读操作
- 当访问old sublist的Page时,会将其移到new sublist的head,实际上几乎所有的read Page操作都会将其移动到new sublist的head。除了预读的Page的情况,预读后如果一直没其他的读操作,该Page最终会从tail剔除
- 随着数据库的运行,一直没有被访问的page会被移向tail,最终被剔除
1.1.2 Making the Buffer Pool Scan Resistant
默认情况下,会出现以下两种case
- 因表扫描,大量数据读入BP,但这数据之后不再被使用
- 由预读加载然后仅访问一次的Page移动到新列表的头部
这两种情况可以将经常使用的page移向到旧子列表中,最终导致被剔除。
因此InnoDB做了Making the Buffer Pool Scan Resistant
这个优化。简单的说就是读取Page插入到BP的时候不是插入到midpoint而是先插入到old sublist的tail ,第一次被读取的时候,再移到midpoint。
另外的一些参数优化可参考官方文档:Making the Buffer Pool Scan Resistant
Configuring InnoDB Buffer Pool Prefetching (Read-Ahead)
1.2 Change Buffer
Change Buffer(以下简称CB) 存在于内存,当对辅助索引(secondary index) 进行DML操作时,BP没有其相应的Page,会将这些变更缓存到CP中。当CB里的Page被read的时候,会被合并到BP中。当脏页超过一定比例时,会将其flush磁盘中。CB的内存默认占BP的25%。
个人认为刷新页到磁盘时没有将CB的数据刷到磁盘,因为CB中的数据只有在被读到的时候才会和BP合并,因此数据库对应的旧数据也不会被读,所以也不急着和flush到磁盘。。
1.2.1 CB带来的提升
原本修改BP不存在的Page需要先从磁盘读取(一次IO操作)到内存,然后写redo log。
引入CB后,会先缓存在CB,然后写redo log。
由于CB的存在,避免了从磁盘读取辅助索引到缓冲池所需的大量随机访问I / O。
辅助索引不支持Change Buffer的情况:
- 如辅助索引包含降序索引列
- 主键包含降序索引
- PS. 降序索引在8.0以上版本才支持。*
1.3. Adaptive Hash Index
Adaptive Hash Index对InnoDB在BP的查询有很大的优化。针对BP中热点页数据,构建索引(一般使用索引键的前缀构建哈希索引)。因为HASH索引的等值查询效率远高于B+ tree,所以当查询命中hash,就能很快返回结果,不用再去遍历B+ tree。
1.4. Log Buffer
Log Buffer是保存要写入磁盘上日志文件的数据的内存区域。默认大小16MB。相关参数设置如下
- innodb_log_buffer_size : 设置大小
- innodb_flush_log_at_trx_commit : 刷新行为。默认值:1, 取值有0,1,2三种。
- 0: 日志每秒刷新到磁盘。 未刷新日志的事务会在mysql崩溃中丢失
- 1: 每次提交事务时,写入并刷新日志到磁盘
- 2: 每次提交事务后写入日志,并每秒刷新一次磁盘。 未刷新日志的事务可能会在mysql崩溃中丢失。
- innodb_flush_log_at_timeout:每几秒刷新日志,取值范围 [1,2700] (second)
二 InnoDB 磁盘结构
2.1 Tablespaces
2.1.1 The System Tablespace
The System Tablespace 是Doublewrite Buffer和Change buffer的储存区域,也有用户创建的表和索引数据。该空间的数据文件通过参数innodb_data_file_path
控制,默认值是ibdata1:12M:autoextend
(文件名为ibdata1,大小略大于12MB,自动扩展)。
8.0之后InnoDB将元数据(以前的.frm文件,存表结构)存在该区域的数据字典中(data dictionary)。
2.1.2 File-Per-Table Tablespaces
File-Per-Table Tablespaces 默认开启,为每个表都独立建一个.ibd文件。 通过参数innodb_file_per_tabl
可以设置关闭,这样的话所有表数据是都存在The System Tablespace的ibdata。
2.1.3 General Tablespaces
General Tablespaces 是通过CREATE TABLESPACE
创建的共享表空间。
2.1.4 Undo Tablespaces
Undo Tablespaces保存的是undo log ,用于回滚事务。
该表空间有rollback segments,rollback segments是用于存 undo log segments, 而undo log segments存的就是undo logs。
mysql启动的时候,默认初始两个undo tablespace(undo_001,undo_002)。因为sql执行前必须要有rollback segments。而两个undo tablespace才支持automated truncation of undo。
2.1.5 Temporary Tablespaces
InnoDB把 Temporary Tablespaces分为两种,session temporary tablespaces 和global temporary tablespace。
session temporary tablespaces存储的是用户创建的临时表和内部的临时表,一个session最多有两个表空间(用户临时表和内部临时表)。
global temporary tablespace储存用户临时表的回滚段(rollback segments )。
临时表的位置在BASEDIR/data/#innodb_temp
下,文件名为temp_*.ibt。
2.2 Doublewrite Buffer
Doublewrite Buffer位于The System Tablespace。在BP的页数据刷到磁盘真正的位置前,会先将数据存在doublewrite buffer。 这步操作是直接将数据作为顺序块,调用OS的fsync()方法写入到doublewrite buffer。
虽然数据写了两次,但是性能还是比两次IO低的。
此外fsync保证了BP中的数据写到磁盘中,即使数据库挂了,还可以从doublewrite buffer中还原数据。
还可以解决页断裂的问题
2.3 Redo log
redo log记录的DML操作的日志,可以用来宕机后的数据前滚。(在log buffer的redo logo日志会在宕机中丢失)
默认的文件名为ib_logfile0 和ib_logfile1。
2.4 undo log
undo log记录数据更改前的快照(感觉就是备份),在数据需要回滚就可以根据undo log恢复。
那些undo log 记录关于在global temporary tablespace 的用户临时表的回滚信息,不会在回滚中恢复。
总结
个人记录,并不一定都是对的,还是得好好看看官方文档,不断琢磨。。。
https://dev.mysql.com/doc/refman/8.0/en/innodb-in-memory-structures.html
https://dev.mysql.com/doc/refman/8.0/en/innodb-on-disk-structures.html