MySQL InnoDB存储引擎(四):磁盘I/O及文件管理
2016 年 10 月 25 日
mysql

    对于DBA,通常需要对数据库服务器的磁盘I/O作相关监控管理,以免发生I/O资源过于紧张,进而导致服务器不稳定及崩溃。除此之外,磁盘空间管理也很重要,需要为数据库保证足够的磁盘空间。ACID设计模型为了保证数据的可靠性(比如,通过写Undo Log等),也需要I/O资源。而InnoDB在运行过程中,会尽可能降低I/O操作优化磁盘文件的组织(如,延迟某些I/O操作直到数据库空闲时,或者为了保证数据一致性,在数据库重启后执行某些恢复操作)。本文将描述InnoDB的磁盘I/O和文件管理的相关细节。

  • InnoDB的磁盘I/O

  • InnoDB会在可能的情况下使用异步磁盘I/O,主要通过创建多个线程来处理I/O操作,与此同时允许其他数据库操作。 在LinuxWindows平台上,InnoDB使用可用的操作系统和库函数来执行“本地”异步I/O,而在其他平台上,InnoDB仍然使用I/O线程,但实际上线程可能会等待I/O请求完成,这种技术也被称为模拟异步I/O

  • 预读(Read-Ahead)

  • InnoDB可以确定如果某些数据将很可能被使用,则会执行预读操作,即将这些数据载入缓冲池,以便在内存中可用,因为对连续的数据执行几次大量的读取请求有可能比执行几次少量且分散的请求更高效,InnoDB中包括以下两种情况会执行预读操作:

    顺序预读:如果InnoDB注意到表空间中的段的访问模式是顺序的,则会预先向I/O系统发出一批数据库页的读取请求;
    随机预读:如果InnoDB注意到表空间中的某些区域似乎正在完全读入缓冲池,则会向I/O系统发出剩余的读操作请求。

    开发人员可以通过缓冲区相关配置来调整预读行为

  • 双写缓冲区(Doublewrite Buffer)

  • InnoDB使用了一种新的文件刷新技术(叫做双写缓冲区(doublewrite buffer)),其提升了当数据库崩溃或断电后恢复的安全性,并且对于大多数Unix系统,其通过减少fsync()调用来提升数据库性能,默认情况该特性会开启(可通过innodb_doublewrite配置)。

    在将页面写入数据文件之前,InnoDB首先将它们写入连续的表空间区域(即双写缓冲区)。 只有在写入并刷新到双写缓冲区后,InnoDB才会将页面写入数据文件中的正确位置。 如果在页面写入过程中有操作系统,存储子系统mysqld进程崩溃(导致页面破坏条件),这样InnoDB可以在恢复期间从双写缓冲区中找到该页面的完整副本。

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

  • 文件空间管理

  • InnoDB系统表空间由配置文件中的选项innodb_data_file_path指定的数据文件组成。 这些文件在逻辑上连续以形成系统表空间。开发人员不能定义系统表空间中表的分配位置。在新创建的系统表空间中,InnoDB将从第一个数据文件开始分配空间。

    为了避免在系统表空间中存储所有表和索引,可以启用innodb_file_per_table配置选项(默认值),该选项将每个新创建的表存储在单独的表空间文件(扩展名为.ibd)中。 对于以这种方式存储的表,磁盘文件中的碎片较少,并且当表被截取(truncate)或丢弃(drop)时,占用空间将返回给操作系统,而不是仍由InnoDB的系统表空间占用。对于innodb_file_per_table的优缺点可见这里

    MySQL 5.7.6开始,您还可以在通用表空间中存储表。 一般表空间是使用CREATE TABLESPACE语法创建的共享表空间,它们可以在MySQL数据目录之外创建,能够保存多个表,并支持所有行格式的表。具体细节可见这里

  • 页面,扩展区,段及表空间(Pages, Extents, Segments, and Tablespaces)

  • 每个表空间都由数据库页面组成。MySQL实例中的每个表空间具有相同的页面大小。默认情况下,所有表空间的页大小为16KB; 您可以通过在创建MySQL实例时指定innodb_page_size选项将页面大小减小到8KB或4KB。从MySQL 5.7.6开始,您还可以将页面大小增加到32KB或64KB。

    数据库页面被分组成大小为1MB的扩展区,即64个连续的16KB页,或128个8KB的页或256个4KB的页,对于32KB的页面大小,扩展区大小为2MB。 对于64KB的页面大小,扩展区大小为4MB。表空间中的“文件”在InnoDB中称为(这些段与回滚段不同,后者实际上包含许多表空间段)。

    当一个在表空间内增长时,InnoDB将前32个页面一次性分配给它。之后,InnoDB开始将整个扩展区分配给段。 InnoDB可以一次将最多4个扩展区添加到大段中,以确保数据良好的顺序性

    在InnoDB中会为每个索引分配两个段,一个段对应B+树的非叶子节点,另一个对应叶子节点。保持叶子节点在磁盘上的连续,可以实现更好的顺序I/O操作,因为这些叶子节点包含实际的表数据。

    表空间中的一些页面包含其他页面的位图,因此InnoDB表空间中的一些扩展区不能作为一个整体分配给段,而只能作为单独的页面分配。

    当通过SHOW TABLE STATUS语句查询表空间中的可用空间时,InnoDB报告表空间中绝对可用的扩展区。 InnoDB总是保留一些区域用于清理及内部使用,这些保留的区不包括在自由空间中。

    当从表中删除数据时,InnoDB会收缩相应的B+树索引,释放的空间是否可用于其他用户,取决于删除模式是否将单个页面或扩展区释放到表空间。删除表(Drop table)删除其中的所有行将保证向其他用户释放这些空间,但删除的行仅会通过清除(purge)操作进行物理删除,这将发生在事务回滚一致性读取不再需要这些被删除数据后。

  • 页面与表数据行的关系

  • 最大行长度略小于数据库页面的一半,可由innodb_page_size设置为4KB,8KB,16KB,32KB或64KB。例如,对于默认的16KB InnoDB页面大小,最大行长度略小于8KB。对于大于32KB的页面,最大行长度均略小于16KB。

    如果一行的数据大小不超过最大行长度,则它将全部存储在页面内。 如果行超过最大行长度,则将选择可变长度列存储在外部页中,直到行符合最大行长度限制。可变长度列的外部页存储行格式有两种:

    COMPACT和REDUNDANT行格式:当在外部页存储可变长度列时,InnoDB将前768个字节存储在行中,其余的存储在外部溢出页中,每个这样的列都有自己的溢出页列表,768字节前缀会存储一个20字节的值,该值存储列的真实长度,并指向溢出列表中存储该列的剩余部分。具体细节可见这里
    DYNAMIC和COMPRESSED行格式:当为外部页存储可变长度列时,InnoDB在该行中存储20字节的指针,并指向溢出列表中存储该列的剩余部分。具体细节可见这里
  • InnoDB检查点(Checkpoints)

  • 使用大日志文件可能会减少检查点(Checkpoints)期间的磁盘I/O。 因此将日志文件的总大小设置为与缓冲池一样大,或者甚至更大,通常是有意义的。虽然之前大日志文件可能使崩溃恢复需要过多的时间,但从MySQL 5.5开始,崩溃恢复的性能增强使得有可能使用大型日志文件,在崩溃后能快速启动(严格地说,这种性能改进可用于MySQL 5.1与InnoDB插件1.0.7和更高版本。与MySQL 5.5,这种改进在InnoDB存储引擎中默认可用)。

  • 检查点(Checkpoints)如何工作

  • InnoDB实现称为模糊检查点(fuzzy checkpointing)的检查点机制。InnoDB以小批量从缓冲池刷新修改的数据库页面,而不是在一个批处理中刷新缓冲池,这将导致在检查点过程中影响对用户SQL语句的处理。

    数据库崩溃恢复期间,InnoDB会查找写入日志文件的检查点标记,并确认标签之前对数据库的所有修改,然后从检查点向前扫描日志文件,将记录的修改应用于数据库。

  • 清除表碎片(Defragmenting a Table)

  • 对于二级索引的随机插入或删除可能导致索引碎片化。 碎片意味着磁盘上索引页的物理排序与页上记录的索引排序并不相近,或者说在分配给索引的64页块中有许多未使用的页。

    碎片化的症状之一就是表实际占用的空间比应该占用的空间多,至于多少则很难确定。 所有InnoDB数据和索引存储在B树中,它们的填充因子可能在50%到100%之间不等。 碎片化的另一个症状是,下面的表扫描语句实际花的时间比应该花的时间更多:

    SELECT COUNT(*) FROM t WHERE non_indexed_column <> 12345;
        

    上面的查询,没有使用索引列查询,将进行全表扫描,对于数据量大的表,执行耗时将比较大。为了加快索引扫描,通常可以通过ALTER TABLE操作来重建表数据,如:

    ALTER TABLE tbl_name ENGINE=INNODB;
        

    另外,也可以通过mysqldump将表数据dump到文本文件,然后drop掉该表数据,并从文本文件中重新加载数据。

    对于升序插入索引从尾开始删除记录时,InnoDB将通过算法保证索引中不会产生碎片,这也是InnoDB默认都会为表生成主键索引的原因之一。

  • 通过TRUNCATE TABLE回收磁盘空间

  • 对于要在截取InnoDB表时回收其磁盘空间,则该表数据必须存储在其自己的.ibd文件中,即在创建表时必须启用innodb_file_per_table。 此外,在被截取的表和其他表之间不能有外键约束,否则TRUNCATE TABLE将执行失败。但是,允许在同一个表中的两列之间的外键约束。

    当表被截取时,它将被删除并在重新创建.ibd文件,并且释放的空间将返回到操作系统。这与截取存储在InnoDB系统表空间(innodb_file_per_table=OFF时创建的表)和存储在共享通用表空间(其中只有InnoDB可以使用表被截取后所释放的空间)的InnoDB表形成对比。

    能够截取表并将磁盘空间返回到操作系统也意味着物理备份可以更小。而截取存储在系统表空间中的表(在innodb_file_per_table= OFF时创建的表)或在通用表空间的表时,未使用的空间将扔保留在表空间中,并不会返回给操作系统。

  • 总结

  • 以上,则是与InnoDB磁盘I/O及文件管理相关的讲述。

  • 参考文献

好人,一生平安。