本文主要涉及两点:
- InnoDB 与 MyISAM 区别;
- InnoDB 架构分析;
-
InnoDB 特性;
注 原文来自架构之路公众号
缓冲池(buffer pool)
缓存表数据与索引数据,把磁盘上的数据加载到缓冲池,避免每次访问都进行磁盘IO,起到加速访问的作用。
(1)缓冲池(buffer pool)是一种常见的降低磁盘访问的机制;
(2)缓冲池通常以页(page)为单位缓存数据;
(3)缓冲池的常见管理算法是LRU,memcache,OS,InnoDB都使用了这种算法;
(4)InnoDB对普通LRU针对以下问题进行了优化:
预读失效
由于预读(Read-Ahead),提前把页放入了缓冲池,但最终MySQL并没有从页中读取数据,称为预读失效。
- 将缓冲池分为老生代和新生代,入缓冲池的页,优先进入老生代,页被访问,才进入新生代,以解决预读失效的问题
MySQL缓冲池污染
当某一个SQL语句,要批量扫描大量数据时,可能导致把缓冲池的所有页都替换出去,导致大量热数据被换出,MySQL性能急剧下降,这种情况叫缓冲池污染
- 页被访问,且在老生代停留时间超过配置阈值的,才进入新生代,以解决批量数据访问,大量热数据淘汰的问题
对应InnoDB里哪些参数?
参数:innodb_buffer_pool_size
介绍:配置缓冲池的大小,在内存允许的情况下,DBA往往会建议调大这个参数,越多数据和索引放到内存里,数据库的性能会越好。
show variables like '%innodb_buffer_pool_size%';
参数:innodb_old_blocks_pct
介绍:老生代占整个LRU链长度的比例,默认是37,即整个LRU中新生代与老生代长度比例是63:37。
- 画外音:如果把这个参数设为100,就退化为普通LRU了。
show variables like '%innodb_old_blocks_pct%';
参数:innodb_old_blocks_time
介绍:老生代停留时间窗口,单位是毫秒,默认是1000,即同时满足“被访问”与“在老生代停留时间超过1秒”两个条件,才会被插入到新生代头部。
show variables like '%innodb_old_blocks_time%';
一 MySQL-写缓冲(change buffer)
什么是InnoDB的写缓冲
在MySQL5.5之前,叫插入缓冲(insert buffer),只针对insert做了优化;现在对delete和update也有效,叫做写缓冲(change buffer)。
它是一种应用在非唯一普通索引页(non-unique secondary index page)不在缓冲池中,对页进行了写操作,并不会立刻将磁盘页加载到缓冲池,而仅仅记录缓冲变更(buffer changes),等未来数据被读取时,再将数据合并(merge)恢复到缓冲池中的技术。写缓冲的目的是降低写操作的磁盘IO,提升数据库性能。
画外音:R了狗了,这个句子,好长。
为什么写缓冲优化,仅适用于非唯一普通索引页呢?
- 如果索引设置了唯一(unique)属性,在进行修改操作时,InnoDB必须进行唯一性检查。也就是说,索引页即使不在缓冲池,磁盘上的页读取无法避免(否则怎么校验是否唯一?),此时就应该直接把相应的页放入缓冲池再进行修改,而不应该再整写缓冲这个幺蛾子。
除了数据页被访问,还有哪些场景会触发刷写缓冲中的数据呢?
还有这么几种情况,会刷写缓冲中的数据:
(1)有一个后台线程,会认为数据库空闲时;
(2)数据库缓冲池不够用时;
(3)数据库正常关闭时;
(4)redo log写满时;
画外音:几乎不会出现redo log写满,此时整个数据库处于无法写入的不可用状态。
对应InnoDB里哪些参数
参数:innodb_change_buffer_max_size
介绍:配置写缓冲的大小,占整个缓冲池的比例,默认值是25%,最大值是50%。
画外音:写多读少的业务,才需要调大这个值,读多写少的业务,25%其实也多了。
参数:innodb_change_buffering
介绍:配置哪些写操作启用写缓冲,可以设置成all/none/inserts/deletes等。
二 double write buffer
MySQL的buffer一页的大小是16K,文件系统一页的大小是4K,也就是说,MySQL将buffer中一页数据刷入磁盘,要写4个文件系统里的页。(如图所示,MySQL里page=1的页,物理上对应磁盘上的1+2+3+4四个格。)

问题:这个操作并非原子,如果执行到一半断电,会不会出现问题呢? 会,这就是所谓的“页数据损坏”

如上图所示,MySQL内page=1的页准备刷入磁盘,才刷了3个文件系统里的页,掉电了,则会出现:重启后,page=1的页,物理上对应磁盘上的1+2+3+4四个格,数据完整性被破坏。
画外音:redo无法修复这类“页数据损坏”的异常,修复的前提是“页数据正确”并且redo日志正常。
如何解决:如何解决这类“页数据损坏”的问题呢?
很容易想到的方法是,能有一个“副本”,对原来的页进行还原,这个存储“副本”的地方,就是Double Write Buffer。
Double Write Buffer,但它与传统的buffer又不同,它分为内存和磁盘的两层架构。
画外音:传统的buffer,大部分是内存存储;而DWB里的数据,是需要落地的。

如上图所示,当有页数据要刷盘时:(步骤2和步骤3要写2次磁盘,这就是“Double Write”的由来。)
第一步:页数据先memcopy到DWB的内存里;
第二步:DWB的内存里,会先刷到DWB的磁盘上;
第三步:DWB的内存里,再刷到数据磁盘存储上;
_画外音:_DWB由128个页构成,容量只有2M。
为什么能解决 DWB为什么能解决“页数据损坏”问题呢?
- 假设步骤2掉电,磁盘里依然是1+2+3+4的完整数据。
画外音:只要有页数据完整,就能通过redo还原数据。 - 假如步骤3掉电,DWB里存储着完整的数据。所以,一定不会出现“页数据损坏”问题。
画外音:写了2次,总有一个地方的数据是OK的。
网上找了一个“页数据损坏”时,MySQL重启过程利用DWB修复页数据的图。

看到,启动过程中:
- InnoDB检测到上一次为异常关闭;
- 尝试恢复ibd数据,失败;
- 从DWB中恢复写了一半的页;
能够通过DWB保证页数据的完整性,但毕竟DWB要写两次磁盘,会不会导致数据库性能急剧降低呢
分析DWB执行的三个步骤:
第一步,页数据memcopy到DWB的内存,速度很快;
第二步,DWB的内存fsync刷到DWB的磁盘,属于顺序追加写,速度也很快;
第三步,刷磁盘,随机写,本来就需要进行,不属于额外操作;
另外,128页(每页16K)2M的DWB,会分两次刷入磁盘,每次最多64页,即1M的数据,执行也是非常之快的。
综上,性能会有所影响,但影响并不大。
结尾(double write buffer)
MySQL有很强的数据安全性机制:
- 在异常崩溃时,如果不出现“页数据损坏”,能够通过redo恢复数据;
- 在出现“页数据损坏”时,能够通过double write buffer恢复页数据;
double write buffer:
- 不是一个内存buffer,是一个内存/磁盘两层的结构,是InnoDB里On-Disk架构里很重要的一部分;
- 是一个通过写两次,保证页完整性的机制;
三 自适应哈希索引(Adaptive Hash Index, AHI)
对于InnoDB的哈希索引,确切的应该这么说:
- InnoDB用户无法手动创建哈希索引,这一层上说,InnoDB确实不支持哈希索引;
- InnoDB会自调优(self-tuning),如果判定建立自适应哈希索引(Adaptive Hash Index, AHI),能够提升查询效率,InnoDB自己会建立相关哈希索引,这一层上说,InnoDB又是支持哈希索引的;
自适应哈希索引
无论是主键索引查询还是普通索引查询在MySQL运行的过程中,如果InnoDB发现,有很多SQL存在这类很长的寻路,并且有很多SQL会命中相同的页面(page),InnoDB会在自己的内存缓冲区(Buffer)里,开辟一块区域,建立自适应哈希所有AHI,以加速查询。
为啥叫“自适应****(adaptive)****”哈希索引?
系统自己判断“应该可以加速查询”而建立的,不需要用户手动建立,故称“自适应”。
系统会不会判断失误,是不是一定能加速?
不是一定能加速,有时候会误判。
当业务场景为下面几种情况时:
- 很多单行记录查询(例如passport,用户中心等业务)
- 索引范围查询(此时AHI可以快速定位首行记录)
- 所有记录内存能放得下
AHI往往是有效的。 画外音:任何脱离业务的技术方案,都是耍流氓。
四 预读
待补充
网友评论