InnoDB内存结构

InnoDB存储引擎支持事务,其设计目标主要面向在线事务处理(OLTP)的应用。其特点是行锁设计、支持外键,并支持非锁定读,即默认读操作不会产生锁。
InnoDB通过使用多版本并发控制(MVCC)来获取高并发性,并且实现了SQL标准的4中隔离级别,默认为REPEATABLE级别。同时,使用一种被称为next-key-locking的策略来避免幻读现象的产生。除此之外,InnoDB存储引擎还提供了插入缓冲(insert buffer)、二次写(double write)、自适应哈希索引(adaptive hash index)、预读(read ahead)等高性能和高可用的功能。
InnoDB存储引擎由内存池,后台线程和磁盘文件三大部分组成。接下来我们就来简单了解一下内存相关的概念和原理。

缓冲池

InnoDB存储引擎是基于磁盘存储的,并将其中的记录按照页的方式进行管理。但是由于CPU速度和磁盘速度之间的鸿沟,基于磁盘的数据库系统通常使用缓冲池记录来提高数据库的的整体性能。
在数据库中进行读取操作,首先将从磁盘中读到的页放在缓冲池中,下次再读相同的页中时,首先判断该页是否在缓冲池中。若在缓冲池中,称该页在缓冲池中被命中,直接读取该页。否则,读取磁盘上的页。
对于数据库中页的修改操作,则首先修改在缓冲池中的页,然后再以一定的频率刷新到磁盘上。页从缓冲池刷新回磁盘的操作并不是在每次页发生更新时触发,而是通过一种称为CheckPoint的机制刷新回磁盘。
所以,缓冲池的大小直接影响着数据库的整体性能,可以通过配置参数innodb_buffer_pool_size来设置。
具体来看,缓冲池中缓存的数据页类型有:索引页、数据页、undo页、插入缓冲(insert buffer)、自适应哈希索引(adaptive hash index)、InnoDB存储的锁信息(lock info)和数据字典信息(data dictionary)。
InnoDB存储引擎的内存区域除了有缓冲池之外,还有重做日志缓冲和额外内存池。InnoDB存储引擎首先将重做日志信息先放到这个缓冲区中,然后按照一定频率将其刷新到重做日志文件中。重做日志缓冲一般不需要设置的很大,该值可由配置参数innodb_log_buffer_size控制。

buffer pool

  • buffer pool 结构

    • Free List:数据库刚启动的时候,LRU 列表为空,此时需要用到的时候直接将Free列表中的页删除,在LRU列表中增加相应的页,维持页数守恒。
    • Flush List:当LRU列表中的页被修改后,称该页为脏页(dirty page),即缓冲池中的页和磁盘上的页数据产生了不一致。这时候数据库会通过checkpoint机制将脏页刷新回磁盘,而Flush 列表中的页即为脏页列表。注意脏页也存在于LRU列表
    • LRU List:
    1. oldsublist默认占buffer pool的3/8(此比例由innodb_old_blocks_pct参数控制);
    2. lru list的分界点是new sublist的链尾与old sublist的链首的交界点;
    3. 当innodb读取一个page到buffer pool(此读取操作可能是用户行为或者是read ahead),此page的初始位置为midpoint;
    4. 当在innodb_old_blocks_time(默认1000ms)再次访问midpoint处数据或者访问old sublist的page会使page 变young,即移动此page到new sublist的头部,用户的读取行为会使此页变young,而预读不会使此页变young
  • buffer pool 特性

    • 线性预读
      如果连续从某一个extent读取超过参数innodb_read_ahead_threshold(默认为56,可取值为0-64)个page,则读取整个extent到内存
    • 随机预读
      如果发现某一个extent的连续13个page在内存中,则读取整个extent到内存,控制随机预读的开关由innodb_random_read_ahead控制
  • buffer pool大小设置

    • 通常分配物理内存的80%给buffer pool
    • innodb_buffer_pool_size控制buffer pool的大小,增加或者减小innodb_buffer_pool_size的最小单位为innodb_buffer_pool_chunk_size(默认为128M),innodb_buffer_pool_size = innodb_buffer_pool_chunk_size * innodb_buffer_pool_instances * n ;5.7之后可以动态修改buffer pool size,通过命令SHOW STATUS WHERE Variable_name=‘InnoDB_buffer_pool_resize_status’;或者错误日志可以监控动态扩展的进度;当动态扩展buffer pool时,其他节点不允许访问buffer pool
    • innodb_buffer_pool_instances:当buffer pool很大时,并发访问buffer pool可能会紧张,因为访问buffer pool前需要获得mutex,并发访问会造成mutex的争用;所以建议把buffer pool分成多个实例,每个实例管理自己的free lists, flush lists, LRUs,不会造成mutex的争用;8.0之前,每个buffer pool实例由自己的mutex保护,8.0之后,buffer pool mutex 被list/hash mutex 取代,进一步减少争用,innodb_buffer_pool_instances的取值范围为1-64,保证每个instance有至少1g的内存,可以使buffer pool的管理更高效

buffer pool flush

  • buffer pool 脏页是由page cleaner threads刷,page cleaner threads的个数是由innodb_page_clearners参数控制,此参数小于innodb_buffer_pool_instances的值,默认是4
  • 刷新毗邻页(innodb_flush_neighbors),此功能由innodb_flush_neighbors参数控制,默认是0,即不flush同一extent的page,当此参数设置为1时,flush同一extent相邻的page,当次参数设置为2时,flush同一extent的page
  • flush 脏页触发条件
    1. sharp checkpoint:完全检查点,数据库正常关闭时,会触发把所有的脏页都写入到磁盘上,这就是完全检查点,数据库正常运行过程中不会使用sharp checkpoint
    2. fuzzy checkpoint:模糊检查点,部分页写入磁盘
    • flush_lru_list checkpoint:innodb_lru_scan_depth(默认为1024)表示page cleaner threads每秒 从lru list的尾部向上扫描并flush脏页的深度

      show variables like '%lru%depth';
      
    • dirty page too much checkpoint当脏页的数量超过innodb_max_dirty_pages_pct_lwm(默认为0%)的时候,会触发flush,当脏页的数量超过innodb_max_dirty_pages_pct(默认为75%),则会触发勤快的flush模式

      show global status like 'Innodb_buffer_pool_pages%t%';
      show global status like '%wait_free';
      
    • master thread checkpoint:以每秒或者每十秒的速度从缓冲池的脏页列表中刷新一定比例的脏页回磁盘,这个过程是异步的,不会阻塞用户线程。

      show variables like '%io_cap%';
      
    • async/sync flush checkpoint:指的是重做日志文件不可用的情况,这时需要强制将一些页刷新回磁盘,而此时脏页是从脏页列表中选取的。log file快满了,会批量的触发数据页回写,这个事件触发的时候又分为异步和同步,不可被覆盖的redolog占log file的比值:75%—>异步、90%—>同步。

  1. 自适应刷新:当redo log 的利用率超过 innodb_adaptive_flushing_lwm(默认10%),会触发自适应脏页刷新,即使innodb_adaptive_flushing参数关闭,innodb_flushing_avg_loops(默认为30,即前30个页的刷新速率)可以有效调整速率
  2. buffer pool 状态的保存和恢复
    为了减少数据库restart后的暖机时间,buffer pool的lru信息会记录到information_schema的 innodb_buffer_page_lru表中(此表记录的不是page内的详细数据信息,而是tablespace id以及page id),并会存储到innodb_buffer_pool_filename指定的file中。
    innodb_buffer_pool_dump_at_shutdown(默认为on):表示关闭数据库的时候dump buffer pool的信息到innodb_buffer_pool_filename指定的文件中
    innodb_buffer_pool_dump_pct (默认为25%):表示关闭数据库的时候dump buffer pool 的比例
    innodb_buffer_pool_load_at_startup (默认为on) :表示开启数据库时加载dump到文件中的page
  3. change buffer
    辅助索引不像聚集索引一样是有序的,通常辅助索引是无序,非唯一的,导致辅助索引在磁盘的插入是随机的,为了减少随机io的产生,引入change buffer,用来缓存没有缓存在buffer pool 的page的变化,当产生变更的page缓存到buffer pool中时,合并change buffer中此页的变更到buffer pool中,减少随机io的产生;innodb_change_buffering:all,none,inserts,deletes,changes,purges,innodb_change_buffer_max_size:25%
    查看change buffer当前占buffer pool的比例:
    SELECT (SELECT COUNT(*) FROM INFORMATION_SCHEMA.INNODB_BUFFER_PAGE WHERE PAGE_TYPE LIKE 'IBUF%') AS change_buffer_pages, (SELECT COUNT(*) FROM INFORMATION_SCHEMA.INNODB_BUFFER_PAGE) AS total_pages, (SELECT ((change_buffer_pages/total_pages)*100)) AS change_buffer_page_percentage;
    

数据页和索引页

Page是Innodb存储的最基本结构,也是Innodb磁盘管理的最小单位,与数据库相关的所有内容都存储在Page结构里。Page分为几种类型,数据页和索引页就是其中最为重要的两种类型

插入缓冲(Insert Buffer)

在InnoDB引擎上进行插入操作时,一般需要按照主键顺序进行插入,这样才能获得较高的插入性能。当一张表中存在非聚簇的且不唯一的索引时,在插入时,数据页的存放还是按照主键进行顺序存放,但是对于非聚簇索引叶节点的插入不再是顺序的了,这时就需要离散的访问非聚簇索引页,由于随机读取的存在导致插入操作性能下降。
InnoDB为此设计了Insert Buffer来进行插入优化。对于非聚簇索引的插入或者更新操作,不是每一次都直接插入到索引页中,而是先判断插入的非聚集索引是否在缓冲池中,若在,则直接插入;若不在,则先放入到一个Insert Buffer中。看似数据库这个非聚集的索引已经查到叶节点,而实际没有,这时存放在另外一个位置。然后再以一定的频率和情况进行Insert Buffer和非聚簇索引页子节点的合并操作。这时通常能够将多个插入合并到一个操作中,这样就大大提高了对于非聚簇索引的插入性能。

两次写(Double Write)

如果说Insert Buffer给InnoDB存储引擎带来了性能上的提升,那么Double Write带给InnoDB存储引擎的是数据页的可靠性。
Double Write由两部分组成,一部分是内存中的double write buffer,大小为2MB,另一部分是物理磁盘上共享表空间连续的128个页,大小也为2MB。在对缓冲池的脏页进行刷新时,并不直接写磁盘,而是通过memcpy函数将脏页先复制到内存中的该区域,之后通过doublewrite buffer再分两次,每次1MB顺序地写入共享表空间的物理磁盘上,然后马上调用fsync函数,同步磁盘,避免操作系统缓冲写带来的问题。在完成doublewrite页的写入后,再讲doublewirite buffer中的页写入各个表空间文件中。
如果操作系统在将页写入磁盘的过程中发生了崩溃,在恢复过程中,InnoDB存储引擎可以从共享表空间中的doublewrite中找到该页的一个副本,将其复制到表空间文件中,再应用重做日志

重做日志(Redo Log Buffer)

当缓冲池中的页的版本比磁盘要新时,数据库需要将新版本的页从缓冲池刷新到磁盘。但是如果每次一个页发送变化,就进行刷新,那么性能开发是非常大的,于是InnoDB采用了Write Ahead Log策略,即当事务提交时,先写重做日志,然后再择时将脏页写入磁盘。如果发生宕机导致数据丢失,就通过重做日志进行数据恢复。
InnoDB存储引擎会首先将重做日志信息先放入重做日志缓冲中,然后再按照一定频率将其刷新到重做日志文件。重做日志缓冲一般不需要 设置得很大,因为一般情况每一秒钟都会讲重做日志缓冲刷新到日志文件中。可通过配置参数innodb_log_buffer_size控制,默认为8MB。
除了每秒刷新机制之外,每次事务提交时重做日志缓冲也会刷新到日志中。InnoDB是事务的存储引擎,其通过Force Log at Commit机制实现事务的持久性,即当事务提交时,必须先将该事务的所有日志写入到重做日志文件进行持久化,然后事务的提交操作完成才算完成。InnoDB的写入机制大致入下图所示。
为了确保每次日志都写入到重做日志文件,在每次讲重做日志缓冲写入重做日志后,必须调用一次fsync操作,将缓冲文件从文件系统缓存中真正写入磁盘。
可以通过innodb_flush_log_at_trx_commit来控制重做日志刷新到磁盘的策略。
当此参数为0时,表示事务commit不立即把 redo log buffer 里的数据刷入磁盘文件的,而是依靠 InnoDB 的主线程每秒(此时间由参数innodb_flush_log_at_timeout控制,默认1s)执行一次刷新到磁盘。此时可能你提交事务了,结果 mysql 宕机了,然后此时内存里的数据全部丢失。
当此参数为1时,表示事务commit后立即把 redo log buffer 里的数据写入到os buffer中,并立即执行fsync()操作
当此参数为2时,表示事务commit后立即把 redo log buffer 里的数据写入到os buffer中,但不立即fsync()

自适应哈希索引(Adaptive Hash Index)

InnoDB会根据访问的频率和模式,为热点页建立哈希索引,来提高查询效率。InnoDB存储引擎会监控对表上各个索引页的查询,如果观察到建立哈希索引可以带来速度上的提升,则建立哈希索引,所以叫做自适应哈希索引。
自适应哈希索引是通过缓冲池的B+树页构建而来,因此建立速度很快,而且不需要对整张数据表建立哈希索引。其有一个要求,即对这个页的连续访问模式必须是一样的,也就是说其查询的条件(WHERE)必须完全一样,而且必须是连续的。

锁信息(lock info)

我们都知道,InnoDB存储引擎会在行级别上对表数据进行上锁。不过InnoDB也会在数据库内部其他很多地方使用锁,从而允许对多种不同资源提供并发访问。数据库系统使用锁是为了支持对共享资源进行并发访问,提供数据的完整性和一致性。

数据字典信息(Data Dictionary)

InnoDB有自己的表缓存,可以称为表定义缓存或者数据字典。当InnoDB打开一张表,就增加一个对应的对象到数据字典。
数据字典是对数据库中的数据、库对象、表对象等的元信息的集合。在MySQL中,数据字典信息内容就包括表结构、数据库名或表名、字段的数据类型、视图、索引、表字段信息、存储过程、触发器等内容。MySQL INFORMATION_SCHEMA库提供了对数据局元数据、统计信息、以及有关MySQL Server 的访问信息(例如:数据库名或表名,字段的数据类型和访问权限等)。该库中保存的信息也可以称为MySQL的数据字典。

你可能感兴趣的:(MySQL,mysql)