MySQL-基础2-InnoDB存储引擎

目录

1. InnoDB体系架构

1.1 后台线程

1.1.1 Master Thread

1.1.2 IO Thread

1.1.3 Purge Thread

1.1.4 Page Cleaner Thread

1.2 内存

1.2.1 缓冲池

1.2.2 LRU列表/Free列表/Flush列表

1.2.3 重做日志缓冲

1.2.4 额外的内存池

2 Checkpoint

3 InnoDB关键特性

3.1 特性1:插入缓存insert buffer

insert buffer升级成change buffer

Insert buffer内部原理

Insert buffer bimap

Merge insert buffer

3.2 特性2:两次写

3.3 特性3:自适应哈希索引

3.4 特性4:异步IO

3.5 特性5:刷新邻接页

4 启动/关闭/恢复

5 名词扩展

5.1 聚集索引

5.2 非聚集索引

6 总结


 


 

1. InnoDB体系架构

1.1 后台线程

InnoDB存储引擎是多线程的模型,因此,多个不同的后台线程分别处理不同的任务:

1.1.1 Master Thread

负责将缓冲池中的数据异步刷新到磁盘,保证数据的一致性,包括脏页的刷新、合并插入缓冲、UNDO页回收等等;

1.1.2 IO Thread

InnoDB存储引擎中大量使用了AIO(Async IO)来处理写IO请求,从而提高数据库性能,而IO Thread主要负责这些IO请求的回调call back处理

1.1.3 Purge Thread

事务被提交后,其使用的undolog可能不再需要,此时就需要PurgeThread来回收已经使用并分配的undo页。

1.1.4 Page Cleaner Thread

Page Cleaner Thread是在InnoDB 1.2.x版本中引入的,其作用是将之前版本中脏页的刷新操作都放入到单独的线程中来完成,其目的是为了减轻原先Master Thread的工作量以及对用户查询线程的阻塞,从而进一步提高InnoDB存储引擎的性能。

1.2 内存

1.2.1 缓冲池

InnoDB存储引擎将表中记录,按照页的方式存储在磁盘上来进行管理,鉴于CPU速度与磁盘速度之间的差距,InnoDB使用内存缓冲池技术来提高数据库的整体性能。

缓冲池本质就是一块内存区域,当数据库读取页时,首先把磁盘读到的页存放在缓冲池中,当后续再读相同的页时,会先去判断该页是否在缓冲池中,若在缓冲池中,则该页在缓冲池中被命中,直接读取该页,否则从磁盘读取该页。

当数据库修改页时,首先修改缓冲池中的页,然后再以一定频率刷新到磁盘上,缓冲池中被修改的页并不会在每次页修改时就刷新到磁盘上,而是通过Checkpoint规则来刷新回磁盘,这样可以提高数据库性能。

到了这里,我们能明显的感受到缓冲池对MySQL数据库性能的影响,因此,建议生产环境采用64位操作系统,通过innodb_buffer_pool_size来配置较大的缓冲池。

缓冲池中缓存的数据页的类型有:索引页、数据页、undo页、插入缓冲inser-buffer、自适应哈希索引、InnoDB存储的锁信息、数据字典信息等,其中索引页 + 数据页占了很大一部分

既然缓冲池作用这么大,那么为了更进一步提高MySQL数据库整体性能,从InnoDB1.0.x开始,允许有多个缓冲池实例,然后表的每个页根据哈希值平均分配到不同的缓冲池实例中,从而减少数据库内部的资源竞争,增加数据库并发处理能力,通过innodb_buffer_pool_instances参数来配置实例个数,默认值为1。

1.2.2 LRU列表/Free列表/Flush列表

通常情况下,缓冲池是通过LRU(Latest Recent Used,即最近最少使用)方法来进行管理,这样使用率最高的页在LRU列表的前端,而使用率最低的页在LRU列表的尾端,当缓冲池无法存放新读取的页时,会首先释放LRU列表尾端的页。

而InnoDB存储引擎,其缓冲池中页默认为16KB,也是同样使用LRU算法来管理缓冲池,只不过InnoDB优化了传统LRU算法:LRU列表中新增midpoint位置,对于新读取的页,不会直接放到LRU列表的头部,而是放到LRU列表midpoint位置,默认midpoint位于LRU列表长度的5/8位置,由innodb_old_blocks_pct控制。

在InnoDB存储引擎中,把midpoint后面的列表称为old列表,之前的列表称为new列表,那为什么InnoDB要新增midpoint呢?

在索引扫描/数据扫描时,会访问数据表中很多页,甚至是全部的页,但这些数据只在本次查询操作所需要,如果把这些页按照LRU算法都放到列表的头部,那么就有可能会把真正的热点数据页从LRU列表头部移除,进而导致需要访问这些热点数据页时,还得从磁盘访问,从而影响缓冲池的效率。

为了进一步控制mid位置新读取页需要等待多久,才会被加入到LRU列表的头部,InnoDB新增innodb_old_blocks_time参数。

当数据库刚启动时,LRU列表是空的,而此时页都存放在Free列表中,当需要从缓冲池中分配页时,先去Free列表看一下是否有可用的空闲页,如果有空闲页则从Free列表中移除,放入到LRU列表中,如果没有空闲,则根据LRU算法淘汰掉LRU列表末尾的页,将这些页占用的内存空间重新分配给新页。

当页从LRU列表的old部分加入到new部分,这个操作称为page made young,而由于innodb_old_blocks_time参数导致页没有从old部分移动到new部分的操作称为page not made young。

InnoDB存储引擎从1.0.x版本开始支持压缩页,把16KB页压缩为1KB/2KB/4KB/8KB,由于页大小变化,对于非16KB的页改由unzip_LRU列表来进行管理。针对不同压缩大小的页,unzip_LRU通过伙伴算法分别进行内存分配/管理,以4KB页为例:

①检查4KB对应的unzip_LRU列表,检查是否有可用的空闲页;

②如果有,则直接使用;

③如果没有,则检查8KB对应的unzip_LRU列表;

④如果得到空闲8KB页,则分成2个4KB页放到4KB对应的unzip_LRU列表;

⑤如果得不到8KB空闲页,则从LRU列表中申请一个16KB的页,然后分成1个8KB、2个4KB页,然后存放到各自对应的unzip_LRU列表中;

LRU列表中被修改的页称为脏页,即缓冲池中的页与磁盘上的页数据不一致,数据库则通过CHECKPOINT机制将脏页刷新到磁盘上,Flush列表中的页就是脏页列表。

脏页同时存在于LRU列表、Flush列表,只不过LRU列表用来管理缓冲池中页的可用性,而Flush列表用来管理脏页刷新回磁盘,彼此工作互不干涉。

1.2.3 重做日志缓冲

InnoDB存储引擎首先把重做日志信息,放到重做日志缓冲redo log buffer,然后存储引擎再以一定频率将日志信息写入重做日志文件。

重做日志缓冲不需要设置很大,因为一般情况下,每一秒就把重做日志缓冲写入到位于磁盘上的日志文件,因此只要保证每秒产生的事务数量在这个缓冲大小内即可,该缓冲大小由innodb_log_buffer_size控制,默认8MB。

引擎会在3种情况下将重做日志缓冲中的内容写入磁盘上的重做日志文件中,进而保证8MB的空间够用:

①Master Thread会每一秒将重做日志缓冲写入到重做日志文件;

②每个事务提交时会将重做日志缓冲写入到重做日志文件;

③当重做日志缓冲池剩余空间小于1/2时,重做日志缓冲将写入到重做日志文件;

1.2.4 额外的内存池

InnoDB存储引擎通过内存堆的方式来管理内存,对一些数据结构自身信息存储时,需要从额外的内存池来申请内存空间,当申请的内存空间不够用时,则继续从额外的内存池中进行申请。

举个例子:缓冲池中既有帧缓冲,还有缓冲控制对象,而这些对象记录了LRU、锁、等待等信息,而这个对象的内存空间是从额外的内存池来申请的。

2 Checkpoint

InnoDB存储引擎需要把内存中的脏页,写入到磁盘上,而判断写入的机制就是Checkpoint,那什么是Checkpoint机制呢?

Checkpoint(检查点)技术目标要解决如下问题:

问题①:宕机后,缩短数据库的恢复时间?

答:当数据库发生宕机后,数据库不需要重做所有日志,鉴于Checkpoint之前的页都已经写入磁盘,因此,只需要对Checkpoint后的重做日志进行恢复即可,从而缩短数据库恢复时间。

问题②:内存缓冲池不够用时,将脏页及时写入到磁盘?

答:缓冲池不够用时,则根据LRU算法释放掉一部分页,当被释放的页还是脏页时,则强制执行Checkpoint,从而将脏页写入磁盘。

问题③:重做日志不可用时,刷新脏页?

答:导致的原因是由于事务数据库对重做日志设计都是循环使用的,通俗理解就是所有的事务都写入几个固定的文件,然后后面的事务信息会覆盖掉前面的事务信息,这就导致了重做日志不可用。

当数据库发生宕机时,数据库恢复不需要这部分对应的重做日志,则这部分信息可以被覆盖、被重新使用,但如果此刻重做日志不能被重用,则强制产生Checkpoint,将缓冲池中的页至少刷新到当前重做日志的位置。(通俗理解就是:当文件内容过时后,后续内容是直接覆盖掉过时的内容,当文件内容可用时,则后续内容追加到当前内容的后面,而不是直接覆盖掉)

Checkpoint技术负责将脏页写入到磁盘,来保证磁盘-内存中数据的一致性,InnoDB引擎内部,有2种Checkpoint:

①Sharp Checkpoint

②Fuzzy Checkpoint

Sharp Checkpont默认工作方式是:当数据库关闭时,负责将所有脏页都写入磁盘,通过innodb_fast_shutdown参数控制;

Fuzzy Checkpoint:由于Sharp Checkpont是把所有脏页都写入磁盘,因此,只能用在数据库关闭场景,而不是数据库正在运行场景,此刻针对数据库运行场景,就使用Fuzzy Checkpoint来将一部分脏页写入到磁盘;

3 InnoDB关键特性

3.1 特性1:插入缓存insert buffer

InnoDB缓冲池中有insert buffer信息,但insert buffer和数据页一样,都是物理页的组成部分。

先说一下该技术出现的原因:

在InnoDB存储引擎中,主键是数据行唯一的标识符,一般情况下行的插入顺序是按照主键依次递增的顺序进行插入的,所以,插入聚集索引一般是顺序的,不需要磁盘的随机读取另一个页中的记录,因此,插入操作速度非常快。但现实是数据表table,不可能只有一个聚集索引,还有很多非聚集的辅助索引,此时就有一个非聚集、非唯一性的索引。

在进行insert操作时,数据页还是按照主键进行顺讯存放(方便非聚集索引使用),但非聚集索引叶子节点的插入则是随机的,由于是随机的,这就导致读取耗费时间、一次读取不成功则多次读取等操作,进而导致非聚集索引执行insert操作性能下降。

为了解决非聚集索引性能低下问题,InnoDB设计了插入缓存insert buffer,其工作过程如下:

当非聚集索引执行insert/update时,不再是每次直接到索引页中,而是先判断要插入的非聚集索引页是否已经在缓冲池中,如果在则直接插入到索引页中,如果不在,则先往插入缓冲池中放入一个insert buffer对象,然后再以一定的频率和场景把insert buffer和辅助索引页子节点进行合并,从而把多个插入操作合并成一个插入操作(类比于JDBC的批处理),从而提高非聚集索引insert/update性能。

Insert buffer使用需要满足2个条件:

①索引是辅助索引(secondary index);

②索引不是唯一的:因为在插入缓冲时,数据库并不会在索引页中进行查询操作来确定待插入记录的唯一性,如果我预先确定记录唯一性的话,则数据库首先查询的话,则查询方式是离散读取方式,这又回到了非聚集索引插入性能低的情况,从而导致insert buffer失去意义。

通俗来讲,insert buffer适合不唯一的非聚集索引。至于insert buffer的缺点则显而易见:在高频率insert时,插入缓冲池会占用过多的缓冲池内存innodb_buffer_pool,插入缓冲池默认可以占到1/2的缓冲池内存。

insert buffer升级成change buffer

InnoDB从1.0.x版本开始新增change buffer,之前insert buffer只针对insert操作缓存,而change buffer针对insert、delete、update都进行缓存,对应的缓存是insert buffer、delete buffer、purge buffer,适用对象还是不唯一的非聚集索引。

例如:delete buffer在缓存delete时,先把记录标记为delete状态,然后再找个时间去真正delete数据。

Insert buffer内部原理

Insert buffer数据结构是一颗B+树,而在MySQL4.1版本之前每张表有一个自己独立的insert buffer B+树,4.1版本之后则全局只有一颗insert buffer B+树来负责对所有表的非聚集索引进行insert buffer,而这个B+树存放在共享表空间中,即ibdata1中。

当我们通过独立表空间ibd文件来恢复table数据时,往往会check table失败,那是因为表的非聚集索引中的数据在insert buffer中即共享表空间中,因此当ibd文件进行恢复后,还要进行repair table来重建该表上所有的非聚集索引。

Insert buffer既然是一颗B+树,也是由叶节点、非叶节点组成,其中的非叶节点存放的是查询用的search key(键值),search key构造如下:

Space

Marker

Offset

Search key一共占用9个字节,其中space表示等待插入记录所在表的表空间id(InnoDB下每个表都有唯一的一个space id),space占用4字节;Marker占用1字节,来兼容老版本的insert buffer;Offset表示页所在的偏移量,占用4字节。(相当于通过space来定位页的位置,然后再通过offset定位页上某个位置)

下面来说一下search key运行过程:

当一个非聚集索引要插入到页(space,offset),如果这个页不在缓冲池中,则InnoDB根据space和offset构造一个search key,然后把这个search key放到insert buffer B+树的非叶节点上。

接着介绍下这颗B+树的叶节点:

插入到insert buffer B+树的叶节点的记录,其构造如下:

Space

Marker

Offset

metadata

Secondary index record

这里的space、marker、offset(这里的offset也称为page_no)与非叶节点中的search key含义相同,也是占9个字节。

Metadata占用4个字节,其结构:ibuf_rec_offset_count占2个字节、ibuf_rec_offset_type占1个字节、ibuf_rec_offset_flags占1个字节。其中的count值是一个整数,用来排序每个记录进入insert buffer时的顺序。

Secondary index record则是实际插入记录的各个字段了,需要13个字节。

Insert buffer bimap

当非聚集索引页(space,page_no)中的记录插入到insert buffer B+树中,为了保证每次merge insert buffer页必须成功,这里有一个特殊的页,来标记每个非聚集索引页(space,page_no)的可用空间,这个特殊页称为insert buffer bitmap。

每个insert buffer bitmap页可以追踪16384个非聚集索引页,即256个区,每个insert buffer bitmap都在16384个页的第二个页中。

Merge insert buffer

合并insert buffer到真正的非聚集索引中,具体分为如下几种情况:

①辅助索引页被读取到缓冲池;

②insert buffer bitmap页追踪到该辅助索引页已无可用空间时;

③master thread;

3.2 特性2:两次写

doublewrite由2部分组成:

①位于内存中的doublewrite buffer,大小为2MB;

②位于磁盘上共享表空间中连续的128个页,即2个区,大小2MB;

工作流程如下:

在对缓冲池的脏页写入磁盘时,先通过memcpy函数将脏页复制到内存中的doublewrite buffer,然后再通过内存doublewrite buffer分2次,每次将1MB的数据按顺序写入磁盘上的共享表空间的2个区,写入后立即调用fsync函数来同步磁盘,避免缓冲写带来的问题,鉴于磁盘上共享表空间中128个页是连续的,因此,这个过程是顺序写入的,开销并不大,在完成128个页的写入后,这才将doublewrite buffer中的页写入到各个表空间文件中,而此时的写入则是离散的。

假如当数据库宕机时,InnoDB只把数据页写了一部分,还没写完就宕机了,这种情况称为部分写失效,此刻可以通过磁盘上的预先保留的128个页的数据,来对这种情况来进行处理,把没有写入的数据来写入,两次写就是用来确保数据页的安全性。

3.3 特性3:自适应哈希索引

哈希是一种非常快的查找方法,这种查找的时间复杂度为O(1),而B+树的查找次数取决于B+树的高度,在生产环境下,B+树高度一般为3-4层,因此需要3-4次查询。

鉴于这种情况,InnoDB会监控各个表上各个索引页的查询,如果可以用哈希索引来提高速度的话,则InnoDB就会建立哈希索引,这种行为就是:自适应哈希索引。

由于自适应哈希索引是通过缓冲池的B+树页来构造的,因此建立速度很快,不需要对整个表构建哈希索引,但想让InnoDB使用该特性,需要满足以下条件:

①对这个页的连续访问模式必须是一样的(访问模式一样指的是:连续查询where后面的条件完全一样);

②以该模式访问了100次;

③数据页通过该模式访问了N次,其中N = 页中记录数 / 16;

3.4 特性4:异步IO

异步IO有2个优势:

①用户无需结果,可以立即发送多个IO操作;

②可以进行io merge操作,把多个IO合并成1个IO操作;

3.5 特性5:刷新邻接页

Flush Neighbor Page刷新邻接页:当写入磁盘一个脏页时,InnoDB会检测该页所在区的所有页,如果也是脏页,则一起写入磁盘,从而通过AIO来把多个IO合并为1个IO;

4 启动/关闭/恢复

关闭数据库时,innodb_fast_shutdown影响InnoDB的行为,参数取值0/1/2,默认值为1:

0表示MySQL关闭时,InnoDB完成所有的full purge和merge insert buffer并且将所有脏页刷新到磁盘上,这个过程需要一段时间,谁也不知道会花费多久,如果进行InnoDB升级则必须把该参数设置为0后,再关闭数据库;

1表示不需要完成full purge和merge insert buffer,但缓冲池中一部分脏页还是会写入到磁盘;

2表示不需要完成full purge和merge insert buffer,也不会将缓冲池中脏页写入磁盘,只是把日志写入到日志文件中,这样就不会有任何事物丢失,但下次启动MySQL时会进行recovery恢复操作;

恢复数据库,则是innodb_force_revovery参数来影响InnoDB行为,取值为1-6:

1表示忽略所检查到的corrupt页;

2表示阻止Master thread线程的运行;

3表示不进行事物回滚操作;

4表示不进行插入缓冲的合并操作;

5表示不查看撤销日志,将未提交的事务看做已提交;

6表示不进行前滚操作;

5 名词扩展

5.1 聚集索引

看名称聚集,基本就能理解其含义,聚集索引有如下特点:

①InnoDB的表中的所有数据行都放在索引上,因此,数据是严格按照顺序存放的,所以不管插入的先后顺序,它在那个物理上的那个位置与插入的先后顺序无关;

②叶子节点存的是整行数据,直接通过这个聚集索引的键值找到某行;

③数据的物理存放顺序与索引顺序是一致的,即:只要索引是相邻的,那么对应的数据一定也是相邻地存放在磁盘上的;

④数据行和相邻的键值紧凑地存储在一起,因为无法同时把数据行存放在两个不同的地方,所以一个表只能有一个聚集索引;

总结:聚簇索引是顺序结构与数据存储物理结构一致的一种索引,并且一个表的聚簇索引只能有唯一的一条;

5.2 非聚集索引

①叶子节点存的是字段的值,通过这个非聚集索引的键值找到对应的聚集索引字段的值,再通过聚集索引键值找到表的某行,类似oracle通过键值找到rowid,再通过rowid找到行;

②InnoDB的聚集索引相当于整张表,而整张表也是聚集索引。默认通过主键聚集数据,如果没有定义主键,则选择第一个非空的唯一索引,如果没有非空唯一索引,则选择rowid来作为聚集索引;

③InnoDB表因为整张表也是聚集索引,select出来的结果是顺序排序的,比如主键字段的数据插入顺序可以是5、3、4、2、1,查询时不带order by得出的结果也是按1、2、3、4、5排序

非聚簇索引记录的物理顺序与逻辑顺序没有必然的联系,与数据的存储物理结构没有关系;一个表对应的非聚簇索引可以有多条,根据不同列的约束可以建立不同要求的非聚簇索引;

6 总结

索引组织表,其行数据以索引形式存放,由于索引组织表索引和数据是在一起的,因此找到索引,就等于找到了行数据。

主键索引的叶子节点存的是整行数据,因此,InnoDB主键索引也被称为聚集索引(clustered index)。

非主键索引的叶子节点内容是主键的值,因此,InnoDB非主键索引也被称为二级索引(secondary index),其需要先定位主键,然后再通过主键去定位数据行,所以才叫做二级。

例如:一个数据表的主键是id,如果通过where id=100方式查询,即主键查询方式,则只需要搜索id这棵B+树即可,因为索引与数据行是存储在一起的;

如果语句是where name=LiLi,即非主键索引查询方式,则需要先搜索name索引树,得到id的值,然后再去id索引树搜索一次,而这个过程又称为回表。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值