innodb架构图
5.5以上默认innodb,最大特性是支持事务。
innodb架构主要由两部分组成:in-memory & on-disk,即内存结构和磁盘结构。这里单独介绍一下内存结构。
架构图:
innodb内存结构
1.innodb内存结构由哪几部分组成?各自作用是什么?
由buffer pool(含buffer pool主要部分、change buffer、adaptive hash index)、log buffer。
buffer pool:缓冲池,缓存表和索引数据,减少磁盘io。innodb_buffer_pool_size也是最核心的参数。
change buffer:占用buffer pool一块区域,属于写缓冲区,针对二级索引(辅助索引)页变更的优化措施,5.5之前叫insert buffer(插入缓冲)。在后续通过其他读操作将这些页加载到缓冲池时被合并。innodb_change_buffer_max_size控制了占buffer pool的比例,默认是25,即占25%,最大是50。
adaptive hash index:无法手动创建自适应哈希索引,只能由innodb自动创建。如果innodb发现了通过B+树检索数据效率低且建立自适应哈希索引能优化的话,则会在buffer pool开辟一块区域建立自适应哈希索引。
log buffer:日志缓冲区,缓存将要落盘log文件(包括redo和undo)的数据,log buffer的内容会定期刷新到磁盘的log文件中。定期刷新的机制使得磁盘io次数变少,提高性能。一般log buffer满了会刷一次,或有大事务也会较频繁写入。可适当的调整innodb_log_buffer_size大小以优化,默认128M。
参考架构图左半部分。
2.什么是buffer pool?
缓冲池,缓存表和索引的数据,目的是减少磁盘io。
由两部分组成:
缓存页(page)
innodb以页为单位,对数据进行划分。以页作为磁盘和内存交互的基本单位,默认大小16KB。
在MySQL启动的时候会按配置申请一块连续的内存区域,会按照页大小将buffer pool内存区划分为一个一个的数据页。
包括:数据页、索引页、undo页、插入缓存页、自适应哈希索引、锁信息。控制块
存储缓存页的描述信息:对应缓存页的表空间信息、对应缓存页在buffer pool的地址信息、页编号。
一个缓存页对应一个控制块,是一一对应的关系。
buffer pool默认大小128M,
page默认16K,
控制块一般为页的5%,约800字节。
当我们查询一条数据的时候,会将这条数据所在页从磁盘加载到buffer pool
innodb是如何判断一个页已经在buffer pool中缓存的?
在MySQL中有一个hash表的数据结构,是一个k-v形式,它使用表空间号+当前数据页编号作为一个key,value是缓存页对应的控制块。当我们需要访问某个页的数据时,先从hash表中根据表空间号+数据页编号判断是否有对应的缓存页。如果有,则直接使用;如果没有,则从free链表中选出一个空闲的缓存页,然后把磁盘中对应的页加载到该空闲缓存页的位置,即一次磁盘io。
3.buffer pool中如何管理page页?
buffer pool底层采用链表的方式管理page页。
参考:
https://blog.csdn.net/wangdamingll/article/details/107348038
page根据状态可分为3种类型:
- free page:空闲页,未被使用的
- clean page:被使用的,但数据是没有修改过的,即缓存页中数据和磁盘页上的数据是一致的
- dirty page:脏页,被使用的,并且数据被修改,且与磁盘数据还不一致
对于上述三种页,innodb使用3中链表结构进行管理:
- free list:表示空闲缓冲区,管理free page。
将所有空闲的缓存页对应的控制块,作为一个一个的节点存到链表中,这个链表就是free list。链表有一个基节点,基节点不记录缓存页信息,只记录链表头尾的地址信息和当前链表节点的个数。此后的节点就是控制块,存储page描述信息,如free page位置等,指向对应的page页。
磁盘加载页的流程:
1.从free list链表中取一个空闲的控制块,即得到对应的空闲缓存页;
2.把此缓存页对应的控制块信息补充完整(比如页所在的表空间,页号之类的信息);
3.将此控制块从free list链表中移除掉,表示该缓存页已被使用,不再空闲;
参考图例:
-
flush list:表示需要刷新到磁盘的缓冲区,管理dirty page。
其内部page是按照修改时间来排序的,用来存储脏页。因为innodb引擎不是每次修改都将缓存页刷到磁盘(log刷盘另说),而是在某特定时间点执行刷新操作,所以需要flush list链表。凡是被修改过的缓存页的控制块,都会作为节点加入flush list链表。
- LRU list:表示正在使用的缓冲区,管理clean page和dirty page。
该缓冲区以midpoint为基点,之前的链表称为new列表区,也是热数据区,用来存放经常被访问的数据,占63%;之后的链表成为old列表区,存放使用较少的数据,占37%。
注意:脏页在flush list和LRU list都存在,两者互不影响。LRU链表负责管理page的可用性和释放,flush链表主要负责管理脏页的刷盘操作。
参考文章:
https://blog.csdn.net/fvdfsdafdsafs/article/details/137889775
4.MySQL为什么没有采用传统LRU算法?
普通的LRU算法
LRU(最近最少使用),新数据从链表头部加入,本来在内存中的page如果使用了则移动到头部;释放空间时从就从末尾淘汰。
普通的LRU链表的缺点
- buffer pool的污染:
当某个SQL扫描了大量数据时,就有可能把buffer pool中所有页(或大量热数据页)都替换出去,导致大量的热数据被淘汰。当这些热数据再次被访问的时候就会产生大量的磁盘io,影响mysql性能。 - 预读失效:
MySQL存在预读机制,因为大部分情况下局部性原理是成立的。普通的LRU算法会将预读页放在LRU链表的头部,淘汰尾部页。一旦这些预读数据没有使用到,相当于预读工作就白做了,还淘汰了尾部缓存页,就降低了缓存命中率,这就是预读失效的后果。
5.MySQL对LRU算法进行了哪些优化?
改进型LRU算法
链表分为new和old两个区域,new区占63%,old区占37%,查看如下参数:
mysql> SHOW VARIABLES LIKE 'innodb_old_blocks_pct';
+-----------------------+-------+
| Variable_name | Value |
+-----------------------+-------+
| innodb_old_blocks_pct | 37 |
+-----------------------+-------+
加入元素时并不是从表头插入,而是从midpoint位置插入。
先将页加载到冷数据区头部,当使用时才会再放到热数据区头部,不使用则被淘汰。这就避免了预读失效导致的性能下降,以及buffer pool污染。
冷数据区数据什么时候被转到热数据区?
- 如果数据页在LRU链表中存在时间超过1s,就将其移动到整个LRU链表的头部;
- 如果该数据页在LRU链表中存在的时间短于1s,则其位置不变;
- 同时满足“在old区停留超过1s”和“被访问”才会插入热区头部。
因为假设加载了一个数据页到缓存,然后过了1s之后你还访问了这个缓存页,说明你后续很可能会经常要访问它,因此只有1s后你访问了这个缓存页,他才会给你把缓存页放到热数据区域的链表头部去。
对应的参数为innodb_old_blocks_time,默认值1000,也就是1000毫秒(1s):
mysql> SHOW VARIABLES LIKE 'innodb_old_blocks_time';
+------------------------+-------+
| Variable_name | Value |
+------------------------+-------+
| innodb_old_blocks_time | 1000 |
+------------------------+-------+
参考:
https://blog.csdn.net/zhanglong_4444/article/details/138469956
6.change buffer
change buffer (写缓冲区),针对于二级索引页更新的优化措施。作用是在进行DML操作时,如果请求的是辅助索引,并且没有在缓冲池中缓存,这时并不会立刻将磁盘页加载到缓冲池,而是在change buffer中记录变更,等待未来数据被读取的时候,再将数据合并恢复到buffer pool中。
什么情况会进行merge?
- 当change buffer中变更操作对应的数据页需要加载到缓存时,加载后innodb就会将这些变更merge到该页上;
- 此外innodb后台线程也会定期加载change buffer中变更操作对应的数据页到缓存,实现合并操作;
- 数据库正常关闭时也会进行merge。
change buffer默认占整个buffer pool的25%,最大允许占用50%。可以根据读写的业务量进行适当调整:
- 写多读少:页面写完后立刻被访问的概率比较小,这时候使用change buffer效果最好,如日志类系统,可将change buffer调大一些。
- 读多写少:不必太大,默认即可。
change buffer更新流程:
- 情况1:对于唯一索引来说,需要将数据页读入内存,判断有没有冲突,插入这个值,语句执行结束。此时change buffer就没用。
-
情况2:对于普通索引来说,直接将更新操作缓存在change buffer中。
过程如下图
为什么change buffer写缓冲区仅适用于辅助索引页?
为什么仅用于非唯一二级索引?因为如果设置了唯一性,则修改时innodb必须进行唯一性校验。因此,如果不在缓冲区,则必须查询磁盘,进行一次io操作,会直接将记录加载到buffer pool,在缓冲池中修改,就不会在change buffer操作了。
7.Log buffer
Log buffer的作用?
log buffer:日志缓冲区,用来保存将要写入磁盘log文件(redo和undo)的数据,默认大小16MB。日志缓冲区的内容会定期刷新到磁盘文件当中。
log buffer作用:用来优化每次更新操作后都要写入redo log而产生的磁盘io过多的问题。日志缓冲区满时,会自动将数据刷新到磁盘;或者当遇到blob或大量更新的大事务时,增加日志缓冲区可节省磁盘io。
关键参数:
- innodb_log_buffer_size:日志缓冲区大小
- Innodb_flush_log_at_trx_commit:日志刷新到磁盘的时机
判断是否需要增加innodb_log_buffer_size,需要依据如下状态:
show status like 'innodb_log_waits';
如果该参数不为0,则应该适当增加大小。
8.adaptive hash index
innodb是否支持哈希索引?
实验:
alter table t1 add index ix_1(col1) using hash;
show index from t1\G
如果我们用语句加hash索引,会发现实际上建的还是B+树。
因为innodb不支持用户手动创建哈希索引,使用存储引擎默认的值进行替代。
innodb会自调优,判断如果建立AHI(adaptive hash index)能提高查询效率,则在内存中建立,不需要人工干预。
为什么要有adaptive hash index?
AHI用于实现对热数据页的一次查询。使用聚簇索引进行数据页定位的时候,需要根据索引树的高度
,从根节点走到叶子节点,通常需要3~4次io才能定位数据。innodb会根据索引字段情况和索引使用情况进行分析,通过自调优的方式,为索引页建立hash索引。不需要的时候会删掉hash索引。
自适应哈希索引是如何提高查询效率?
AHI作用的目标是频繁查询的数据页和索引页,由于数据页是聚簇索引的一部分,因此AHI是建立在索引之上的索引。
对于二级索引,如果命中AHI,则直接从AHI中获取二级索引页的记录指针,再根据主键沿着聚簇索引查询。
对于聚簇索引,如果聚簇索引查询命中AHI,则直接返回目标数据页的记录指针,即可通过此记录指针直接定位数据页。
通过二级索引访问记录过程如图:
不管是聚簇索引还是普通索引,记录定位的寻路路径都是比较长的。MySQL的运行过程中,如果发现有很多寻路很长(B+树层数多,回表次数多)SQL,或有很多SQL命中相同的页,innodb就会在内存中开辟一块区域建立自适应哈希索引,加速查询。
hash数据结构都是包含k-v(键值)的。
在AHI中,key就是经常访问的索引键值,value就是该索引键值匹配的完整记录所在page位置。
通过adaptive hash index访问记录的过程和聚簇索引对比如图:
如果是二级索引想获取整行数据还是要回一次表。