Innodb核心原理之内存结构

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访问记录的过程和聚簇索引对比如图:


如果是二级索引想获取整行数据还是要回一次表。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容