大家好,我是此林。
今天来介绍下InnoDB底层架构。
1. 磁盘架构
我们所有的数据库文件都保存在 /var/lib/mysql目录下。
由于我这边是docker部署的mysql,用如下命令查看mysql数据挂载。
docker inspect mysql-master
如下图,目前只有一个数据库 db_user,数据库本质上是文件夹的形式。
在db_user文件夹下,目前只有一张表,表空间就是idb文件形式。
表空间idb文件以B+树的形式组织存储,其中B+树的非叶子结点存储索引段,叶子结点存储的是数据段。此外还有回滚段。段用来管理多个区。
区是表空间的单元结构,每个区大小为1M,一个区有64个连续的页。
页,是InnoDB存储引擎磁盘管理的最小单元,每个页默认大小为16K。为了保证页的连续性,Inn哦DB存储引擎每次从磁盘申请4-5个区。
行,InnoDB存储引擎数据是按行存放的。
所有文件介绍:
1. db_user:自定义的数据库
2. mysql:mysql自带数据库,包含用户、权限和管理信息表
3. performance_schema、sys:mysql自带数据库,用于mysql性能监控
4. ib_logfile0、ib_logfile1:redo_log(重做日志)
5. binlog.000001、binlog000002:binlog(二进制日志)
6. undo_001、undo_002:undo_log(回滚日志)
2. 内存架构
1. BufferPool
数据表都保存在磁盘文件中,而用户是无法直接操作磁盘文件的,必须先把数据文件加载到内存中,用户对内存中的数据进行增删改操作,最后再以一定的频率把内存中的数据刷新到磁盘中。
InnoDB内存架构中,BufferPool(缓冲区)就是类似的作用。
BufferPool 中,以页(Page)为单位,每次它会去磁盘中加载整页(16K)数据。这样做的好处是减少磁盘IO,相当于缓存的作用。
BufferPool 中的 Page有三种类型:
1. 空页(free page),空闲页,未被使用
2. clean page,被使用的页,但是数据没有被修改过
3. 脏页(dirty page),被使用的page,数据被修改过,也就是和磁盘中数据不一致的页。
2. ChangeBuffer
更改缓冲区(针对非唯一的二级索引),在执行DML语句(增删改语句)时,如果这些这些数据Page不在BufferPool中,不会区操作磁盘,而是先把数据变更存在更改缓冲区中,未来数据被读取时,再将数据合并恢复到BufferPool中,最后再刷新到磁盘。
存在的意义?
和聚簇索引不同,二级索引通常是非唯一的,并且每次以相对随机的顺序插入二级索引。同样,删除和更新可能会影响索引树中不相邻的二级索引页,如果每一次都操作一次磁盘IO,会造成大量的磁盘IO,有了ChangeBuffer后,我们可以在缓冲池中进行合并操作,减少磁盘IO。
3. Adaptive Hash Index
InnoDB不支持Hash索引,它只支持B+树索引,我们目前所有的索引,底层结构都是B+树,主要原因是Hash索引虽然快,但是只支持等值匹配,不支持范围查询。
自适应Hash索引,用于优化对BufferPool的查询。InnoDB 存储引擎会监控对表上各个索引页的查询,如果观察到Hash索引可以提升速度,则建立Hash索引。系统自动优化,无需人工干预。
查询参数:
show variables like '%hash%';
默认是开启的。
4. LogBuffer
日志缓冲区,用来保存要写入到磁盘中的log日志数据(redo log,undo log)。默认大小为16MB,日志缓冲区的日志会定期刷到磁盘中。
参数:
show variables like 'innodb_log_buffer_size'; # 缓冲区大小
show variables like 'innodb_flush_log_at_trx_commit'; # 日志刷新到磁盘的时机
innodb_flush_log_at_trx_commit:
1: 日志在每次事务提交时写入并刷新磁盘
0:每秒将日志写入并刷新磁盘一次
2:日志在每次事务提交后写入,并且每秒刷新一次到磁盘中。
3. 后台线程
1. Master Thread
核心后台线程,负责调度其他线程,还负责将缓冲区中的数据异步刷新到磁盘中,保持数据的一致性,还包括脏页的刷新、合并插入缓存、undo页的回收。
2. IO Thread
在InnoDB中大量使用AIO(异步非阻塞)来处理IO请求,极大提高数据库性能。
IO Thread主要负责这些IO请求的回调。
命令:
show engine innodb status;
3. Purge Thread
主要用于回收事务已经提交的undo_log,在事务提交之后,undo log已经无用,需要回收。
4. Page Cleaner Thread
协助Master Thread刷新脏页到磁盘的线程,减轻Master Thread的工作压力,减少阻塞。
4. InnoDB 事务原理
事务特性(ACID):
1. 原子性:事务操作不可分割,要么同时成功,要么同时失败。
2. 一致性:事务完成时,所有数据必须保证一致性。
3. 隔离性:数据库系统提供的隔离机制,保证事务在不受外部并发的影响的独立环境下运行。
4. 持久性:事务一旦 提交,对数据库的改变就是永久的。
原子性、一致性由undo_log来实现,
持久性由redo_log来实现,
隔离性由锁和MVCC机制来实现。
1. redo_log重做日志
我们知道,在执行update、delete、insert操作的时候,实际操作的是BufferPool内存中的数据,
但是BufferPool中的数据是由后台线程以一定的频率或由操作系统来决定何时同步到磁盘中的。
若BufferPool数据没来得及刷到磁盘中,服务器宕机,那么就会导致数据丢失。
redo_log出现后,流程变化如下:
1. 变更BufferPool中的数据
2. 数据页变化写入redo_log_buffer(内存)
3. 事务提交后,redo_log_buffer刷新到磁盘(redo_log)中。
4. 之后即使BufferPool没来得及刷新到磁盘,也可以通过redo_log来恢复。
问:redo_log不是多此一举吗?为什么不操作完BufferPool后,直接把脏页刷新到磁盘里?
答:
1. 我们一般在事务操作很多条记录,这些记录一般都是随机操作数据页的,此时将涉及大量的磁盘IO,性能极低。
2. 而redo_log日志文件写入都是追加顺序写入,性能高于随机磁盘IO。这种机制叫WAL(Write-Ahead-logging)——先写日志。
3. 当BufferPool中脏页的数据成功刷新到磁盘中,redo_log日志文件会被定期删除。