MySQL InnoDB存储引擎(二):架构
2016 年 08 月 30 日
mysql

    前一篇文章主要讲述MySQLInnoDB存储引擎的基础,特性,优势等,本文主要聚焦于InnoDB的架构设计,对其内部的主要组件作相应介绍。

  • 缓冲区(Buffer Pool)

  • 缓冲区InnoDB用于缓存表和索引数据的一块主内存区,对于频繁访问的数据,可以直接从该缓冲区获取。对于专用的数据库服务器,通常可以分配80%的物理内存用于该缓冲区。为了提升大批量读操作的性能,缓冲区会被分为多个数据页,每页包含了多行记录。为了提升缓存管理的性能,缓冲区链表页组成;对于很少访问的数据,将基于LRU算法,从缓冲区中清除。关于InnoDB Buffer Pool的细节,可见这里

  • 更新缓冲区(Change Buffer)

  • 更新缓冲区是一种特殊的数据结构。当相关页不在缓冲区里时,更新缓冲区可用于缓存二级索引页的变化。缓冲区的变化可由INSERTUPDATEDELETE等DML语句引起,随后当页面被加载进缓冲区后,这些变化将与其他读操作进行合并。

    不像聚簇索引二级索引通常并不是唯一的,并且插入二级索引时,会以相对随机的顺序。类似地,删除或更新操作可能会影响位于索引树不相邻的二级索引页。当相关页被其他操作读进缓冲区后,随后与缓存的变化进行合并,这将避免后续从磁盘读取二级索引页带来的I/O操作。

    周期性地,当系统大部分空闲或缓慢关闭时,运行的清除操作(purge),会将更新的索引页写入磁盘。 与立即写入每个更新到磁盘相比,清除操作(purge)可以更高效地将一系列索引值写入磁盘块。

    当有大量二级索引要更新,并且有许多行受影响时,更新缓冲区合并可能需要几个小时。在此期间,磁盘I/O增加,这可能导致磁盘相关查询明显变慢。更新缓冲区合并也可以在事务提交后发生。事实上,更新缓冲区合并也可能在服务器关闭和重新启动后发生。

    在内存中,更新缓冲区占用了InnoDB缓冲区的一部分。在磁盘上,更新缓冲区系统表空间的一部分,以便在数据库重启后能够保留索引变化

  • 监控更新缓冲区

  • 下面一些操作可用于监控监控更新缓冲区

    InnoDB的标准监控输出中(具体监控细节可见这里),则包含了更新缓冲区相关的信息:
    mysql> SHOW ENGINE INNODB STATUS;
    ...
    -------------------------------------
    INSERT BUFFER AND ADAPTIVE HASH INDEX
    -------------------------------------
    Ibuf: size 1, free list len 0, seg size 2, 0 merges
    merged operations:
     insert 0, delete mark 0, delete 0
    discarded operations:
     insert 0, delete mark 0, delete 0
    Hash table size 34679, node heap has 0 buffer(s)
    Hash table size 34679, node heap has 0 buffer(s)
    Hash table size 34679, node heap has 0 buffer(s)
    Hash table size 34679, node heap has 0 buffer(s)
    Hash table size 34679, node heap has 2 buffer(s)
    Hash table size 34679, node heap has 0 buffer(s)
    Hash table size 34679, node heap has 0 buffer(s)
    Hash table size 34679, node heap has 0 buffer(s)
    0.00 hash searches/s, 0.00 non-hash searches/s
    ...
    		
    另外,也可从INFORMATION_SCHEMA.INNODB_METRICS表中查询更新缓冲区相关的信息(具体细节可见这里):
    mysql> SELECT NAME, COUNT, COMMENT FROM INFORMATION_SCHEMA.INNODB_METRICS WHERE NAME LIKE '%ibuf%';
    +-----------------------------------------+-------+-------------------------------------------------------------+
    | NAME                                    | COUNT | COMMENT                                                     |
    +-----------------------------------------+-------+-------------------------------------------------------------+
    | buffer_page_read_index_ibuf_leaf        |     0 | Number of Insert Buffer Index Leaf Pages read               |
    | buffer_page_read_index_ibuf_non_leaf    |     0 | Number of Insert Buffer Index Non-Leaf Pages read           |
    | buffer_page_read_ibuf_free_list         |     0 | Number of Insert Buffer Free List Pages read                |
    | buffer_page_read_ibuf_bitmap            |     0 | Number of Insert Buffer Bitmap Pages read                   |
    | buffer_page_written_index_ibuf_leaf     |     0 | Number of Insert Buffer Index Leaf Pages written            |
    | buffer_page_written_index_ibuf_non_leaf |     0 | Number of Insert Buffer Index Non-Leaf Pages written        |
    | buffer_page_written_ibuf_free_list      |     0 | Number of Insert Buffer Free List Pages written             |
    | buffer_page_written_ibuf_bitmap         |     0 | Number of Insert Buffer Bitmap Pages written                |
    | ibuf_merges_insert                      |     0 | Number of inserted records merged by change buffering       |
    | ibuf_merges_delete_mark                 |     0 | Number of deleted records merged by change buffering        |
    | ibuf_merges_delete                      |     0 | Number of purge records merged by change buffering          |
    | ibuf_merges_discard_insert              |     0 | Number of insert merged operations discarded                |
    | ibuf_merges_discard_delete_mark         |     0 | Number of deleted merged operations discarded               |
    | ibuf_merges_discard_delete              |     0 | Number of purge merged  operations discarded                |
    | ibuf_merges                             |     0 | Number of change buffer merges                              |
    | ibuf_size                               |     1 | Change buffer size in pages                                 |
    | innodb_ibuf_merge_usec                  |     0 | Time (in microseconds) spent to process change buffer merge |
    +-----------------------------------------+-------+-------------------------------------------------------------+
    		
    INFORMATION_SCHEMA.INNODB_BUFFER_PAGE表提供了缓冲区中每页相关的元数据信息,包括更新缓冲索引页更新缓冲位图页更新缓冲索引页由表示页面类型PAGE_TYPE.IBUF_INDEX标识,更新缓冲位图页由页面类型IBUF_BITMAP标识。(查询INNODB_BUFFER_PAGE有可能会引起明显的性能靠小,因此为了避免影响性能,最好在测试服务器上执行相关查询)。比如,可以通过查询INNODB_BUFFER_PAGE,确定更新缓冲索引页更新缓冲位图页缓冲区页面的占比:
    mysql> 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;
    +---------------------+-------------+-------------------------------+
    | change_buffer_pages | total_pages | change_buffer_page_percentage |
    +---------------------+-------------+-------------------------------+
    |                  31 |        8192 |                        0.3784 |
    +---------------------+-------------+-------------------------------+
    		

    具体其他细节可见这里

    Performance Schema提供了更新缓冲区互斥锁等高级监控信息,可使用下面的查询语句:
    mysql> SELECT * FROM performance_schema.setup_instruments WHERE NAME LIKE '%wait/synch/mutex/innodb/ibuf%';
    +-------------------------------------------------------+---------+------
    | NAME                                                  | ENABLED | TIMED |
    +-------------------------------------------------------+---------+-------+
    | wait/synch/mutex/innodb/ibuf_bitmap_mutex             | NO      | NO    |
    | wait/synch/mutex/innodb/ibuf_mutex                    | NO      | NO    |
    | wait/synch/mutex/innodb/ibuf_pessimistic_insert_mutex | NO      | NO    |
    +-------------------------------------------------------+---------+-------+
    		

    有关InnoDB互斥锁等监控,可见这里

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

  • 在具有适当工作负载和足够内存的缓冲区的系统中,在不牺牲任何事务特性可靠性的情况下,自适应哈希(AHI)使得InnoDB更像一个内存数据库,可通过innodb_adaptive_hash_index(默认)选项开启自适应哈希,或者--skip-innodb_adaptive_hash_index禁用自适应哈希

    基于观察到的搜索模式,MySQL使用索引键的前缀构建一个散列索引。索引键的前缀可以是任意长度,并且可能只有B树中的部分值在哈希索引中。哈希索引是根据那些经常访问的页面的索引而构建的。

    如果某个表适合完全放入祝内存,哈希索引可以通过启用对任意记录的直接查找,并将索引值转换为一种指针,从而加快查询效率。InnoDB具有监控索引搜索的机制,如果InnoDB注意到查询可以从散列索引中受益,则会自动构建哈希索引。

    对于一些工作负载,散列索引带来的查找加速,大大超过监视索引查找和维护散列索引结构的额外工作。有时,为了保护自适应哈希索引的访问,读/写锁可能在繁重的工作负载下(例如多个并发join)成为争用条件。通常,具有LIKE运算符%通配符的查询也往往不会从AHI中受益。对于不需要自适应哈希索引的工作负载,关闭它可以减少不必要的性能开销。因为很难预测此功能是否适用于特定系统,应该考虑针对实际工作负载作启用和禁用的基准测试。 在MySQL5.6及更高版本中的架构中,使得更多的工作负载适合禁用自适应哈希索引

    哈希索引总是基于表上的现有B树索引构建的。InnoDB可以以任意长度的前缀作为索引键来构建哈希索引,这取决于InnoDB观察到的B树索引的搜索模式。哈希索引可以是部分的,即仅构建那些经常被访问索引页。

    您可以在SHOW ENGINE INNODB STATUS命令的输出的SEMAPHORES部分中,监控的自适应散列索引的使用情况。若看到许多线程等待在btr0sea.c中创建的读写锁,那么可以尝试禁用自适应哈希索引。更多有关自适应哈希索引可见这里

  • 系统表空间(System Tablespace)

  • InnoDB系统表空间包含InnoDB数据字典(与InnoDB对象相关的元数据),并且作为双写缓冲区更新缓冲区撤销日志的存储区域。系统表空间还包含在系统表空间中创建的用户所创建的表和索引数据。系统表空间被认为是共享表空间,因为它由多个表共享。

    系统表空间由一个或多个数据文件表示。默认情况下,在MySQL数据目录中会创建一个名为ibdata1的系统数据文件。该系统数据文件的大小和数量由启动选项innodb_data_file_path控制。进一步配置管理系统表空间,可见这里

  • 每个表的表空间(File-Per-Table Tablespaces)

  • 每个表的文件表空间是在其自己的数据文件而不是在系统表空间中创建的单表表空间。 在启用innodb_file_per_table选项时,表将在其对应的表空间中被创建,若未启用该选项,将在系统表空间中创建InnoDB表。每个表的表空间由单个.ibd数据文件表示,默认情况下在数据库数据目录中创建。

    单表表空间文件支持DYNAMICCOMPRESSED行格式,这些格式支持诸如页外存储可变长度数据表压缩等功能,有关这些功能以及单表表空间的其他优点的信息可见这里

  • 撤销表空间

  • 撤销表空间由一个或多个包含撤销日志文件组成, 但需要使用innodb_undo_tablespacesinnodb_undo_directory配置选项将撤销日志系统表空间分开,该特性在MySQL 5.6中引入。具体细节可见这里

  • 撤销日志(Undo Log)

  • 撤销日志被用于存储由执行事务产生的数据修改的副本,如果另一个事务需要查看原始数据(作为一致性读操作的一部分),并从该存储区获取未被修改的数据。默认情况下,此区域实际上是系统表空间的一部分。但是,从MySQL 5.6.3开始,撤销日志可以存储在单独的撤销表空间中。细节信息可见这里

    InnoDB支持128个撤销日志。从MySQL 5.7.2开始,128个撤销日志中的32个被保留为临时表事务的非重做撤销日志。 每个更新临时表的事务(不包括只读事务)都会分配有两个撤销日志,一个启用重建的撤消日志和一个非重建的撤消日志。 只读事务仅分配非重建的撤消日志,因为只读事务只允许修改临时表。

    剩下可用的96个撤销日志,每个支持多达1023个并发数据修改事务,总共限制大约96K个并发事务修改数据(96K限制假定事务不修改临时表)。 如果所有修改数据的事务同时也修改临时表,则总限制为大约32K个。有关为临时表事务保留的撤销日志的详细信息,可见这里

  • InnoDB数据字典(Data Dictionary)

  • InnoDB数据字典内部系统表组成,其中包含用于跟踪对象(如表,索引和表列)的元数据。 元数据物理上位于InnoDB系统表空间中。由于历史原因,数据字典元数据在一定程度上与存储在InnoDB表元数据文件(.frm文件)中的信息重叠。

  • 双写缓冲区(Doublewrite Buffer)

  • 双写缓冲区是位于系统表空间中的一个存储区域,InnoDB首先将缓冲区中的数据页刷新到该区域,最终会讲数据页被写入合适的数据文件,即只有在刷新页面并将页面写入到双重缓冲区之后,InnoDB才会将页面写入合适的位置。 如果在页面写入过程中,有操作系统,存储子系统或mysqld进程崩溃,InnoDB稍后可以在崩溃恢复期间从双写缓冲区中找到该页面的完好副本。

    尽管数据总是被写入两次,但是双写缓冲区并不需要两倍I/O开销或两倍I/O操作,数据将作为大顺序块,并调用一次系统函数fsync(),被写入双写缓冲区。大多数情况下,双写缓冲区默认是开启的,可以通过配置innodb_doublewrite=0来禁用双写缓冲区

    MySQL 5.7.4开始,如果系统表空间文件(ibdata文件)位于支持原子写入的Fusion-io设备上,则会自动禁用双写缓冲区,并对所有数据文件使用Fusion-io原子写入。 因为双重缓冲区设置是全局的,所以对于驻留在非Fusion-io硬件上的数据文件也禁用双写缓冲区。 此功能仅在Fusion-io硬件上支持,并且仅对Linux上的Fusion-io NVMFS启用。要充分利用此功能,建议将innodb_flush_method设置O_DIRECT

  • 重建日志(Redo Log)

  • 针对重建日志刷新的组提交(Group Commit for Redo Log Flushing)

  • 重建日志是一种基于磁盘的数据结构,用于在崩溃恢复期间,纠正由不完整事务写入的数据。在正常操作期间,重建日志会编码由SQL语句低级API调用对InnoDB表数据进行修改的请求。在数据库初始化期间以及在接受连接之前,将自动重新执行那些在异常关闭之前未完成数据文件更新的修改请求。有关span class="highlight">重建日志在崩溃恢复中的作用的详细信息,可见InnoDB恢复过程

    默认情况下,重建日志会物理表示为磁盘上的一组文件,名为ib_logfile0ib_logfile1。MySQL以循环方式写入重建日志文件。 重建日志中的数据按照受影响的记录进行编码,该数据被统称为重建

  • 重建日志缓冲(Redo Log Buffer)

  • 重建日志缓冲区是用于保存要写入重建日志数据的一块内存区域。 重建日志缓冲区大小由Minnodb_log_buffer_size配置选项定义。重建日志缓冲区会定期刷新到日志文件。 容量大的重建日志缓冲区可以使大型事务运行,而不需要在事务提交之前将重建日志写入磁盘。 因此,对于更新,插入或删除多行的事务,使用更大的日志缓冲区可以节省磁盘I/O

    innodb_flush_log_at_trx_commit选项可以控制重建日志缓冲区中的数据如何写到日志文件。innodb_flush_log_at_timeout选项可以控制重建日志刷新频率。

  • 临时表空间(Temporary Tablespace)

  • InnoDB临时表撤销日志

  • MySQL 5.7.1中,引入了用于非压缩的InnoDB临时表及相关对象的临时表空间。 配置选项innodb_temp_data_file_path定义临时表空间数据文件的相对路径。如果未定义innodb_temp_data_file_path,则在数据目录中创建一个名为ibtmp1的可自动扩展的12MB大小的数据文件。临时表空间在每个服务器启动时会重新创建,并得到一个动态生成的空间ID,这有助于避免与现有空间ID的冲突。临时表空间不能驻留在裸设备上。 如果无法创建临时表空间,将禁止启动。

    临时表空间在正常关闭或异常初始化时会被删除。但数据库发生崩溃时,并不会被删除临时表空间,在这种情况下,数据库管理员可以手动删除临时表空间或使用相同的配置重新启动服务器,这会删除和重新创建临时表空间

  • 总结

  • 以上,则是有关InnoDB架构中的核心组件,如缓冲区撤销日志等, 对于各组件如何工作及协同,可以参见具体的文章,后续将针对每个组件进行探究。

  • 参考文献

好人,一生平安。