之前介绍的文件都是 MySQL数据库本身的文件,和存储引擎无关。除了这些文件外,每个表存储引擎还有其自己独有的文件。本节将具体介绍与 InnoDB存储引擎密切相关的文件,这些文件包括重做日志文件、表空间文件。
1、表空间文件
InnoDB采用将存储的数据按表空间(tablespace)进行存放的设计。在默认配置下会有一个初始大小为10MB,名为 ibdata1的文件。该文件就是默认的表空间文件(tablespace file),用户可以通过参数 innodb_data_file_path对其进行设置,格式如下
innodb_data_file_path=datafile_spec1[;datafile_spec2]...
用户可以通过多个文件组成一个表空间,同时制定文件的属性,如:
[mysqld]
innodb_data_file_path=/db/ibdata1:2000M;/dr2/db/ibdata2:2000M:autoextend
这里将/db/ibdata1和/dr2/db/ibdata2两个文件用来组成表空间。若这两个文件位于不同的磁盘上,磁盘的负载可能被平均,因此可以提高数据库的整体性能。同时,两个文件的文件名后都跟了属性,表示文件 idbdata1的大小为2000MB,文件 ibdata2的大小为2000MB,如果用完了这2000MB,该文件可以自动增长(autoextend)设置 innodb_data_file_path参数后,所有基于 InnoDB存储引擎的表的数据都会记录到该共享表空间中。若设置了参数 innodb_file_per_table,则用户可以将每个基于InnoDB存储引擎的表产生一个独立表空间。独立表空间的命名规则为:表名.ibd。通过这样的方式,用户不用将所有数据都存放于默认的表空间中。下面这台 MySQL数据库服务器设置了 innodb_file_per_table,故可以观察到:
mysql> SHOW VARIABLES LIKE 'innodb_file_per_table';
+-----------------------+-------+
| Variable_name | Value |
+-----------------------+-------+
| innodb_file_per_table | ON |
+-----------------------+-------+
1 row in set (0.00 sec)
mysql> system sudo ls /usr/local/mysql/data/mysql
columns_priv.MYD event.MYI help_category.frm innodb_table_stats.frm procs_priv.MYD slave_master_info.frm tables_priv.MYI time_zone_transition_type.frm
columns_priv.MYI event.frm help_category.ibd innodb_table_stats.ibd procs_priv.MYI slave_master_info.ibd tables_priv.frm time_zone_transition_type.ibd
columns_priv.frm func.MYD help_keyword.frm ndb_binlog_index.MYD procs_priv.frm slave_relay_log_info.frm time_zone.frm user.MYD
db.MYD func.MYI help_keyword.ibd ndb_binlog_index.MYI proxies_priv.MYD slave_relay_log_info.ibd time_zone.ibd user.MYI
db.MYI func.frm help_relation.frm ndb_binlog_index.frm proxies_priv.MYI slave_worker_info.frm time_zone_leap_second.frm user.frm
db.frm general_log.CSM help_relation.ibd plugin.frm proxies_priv.frm slave_worker_info.ibd time_zone_leap_second.ibd
db.opt general_log.CSV help_topic.frm plugin.ibd server_cost.frm slow_log.CSM time_zone_name.frm
engine_cost.frm general_log.frm help_topic.ibd proc.MYD server_cost.ibd slow_log.CSV time_zone_name.ibd
engine_cost.ibd gtid_executed.frm innodb_index_stats.frm proc.MYI servers.frm slow_log.frm time_zone_transition.frm
event.MYD gtid_executed.ibd innodb_index_stats.ibd proc.frm servers.ibd tables_priv.MYD time_zone_transition.ibd
由于设置参数 innodb_file_per_table=ON,因此产生了单独的.ibd独立表空间文件。需要注意的是,这些单独的表空间文件仅存储该表的数据、索引和插入缓冲 BITMAP等信息,其余信息还是存放在默认的表空间中。图显示了 InnoDB存储引擎对于文件的存储方式:
2、重做日志文件
在默认情况下,在 InnoDB存储引擎的数据目录下会有两个名为 ib_logfile0和ib_logfile1的文件。在 MySQL官方手册中将其称为 InnoDB存储引擎的日志文件,不过更准确的定义应该是重做日志文件(redo log file)。为什么强调是重做日志文件呢?因为重做日志文件对于 InnoDB存储引擎至关重要,它们记录了对于 InnoDB存储引擎的事务日志。
当实例或介质失败(media failure)时,重做日志文件就能派上用场。例如,数据库由于所在主机掉电导致实例失败, InnoDB存储引擎会使用重做日志恢复到掉电前的时刻,以此来保证数据的完整性。
每个 InnoDB存储引擎至少有1个重做日志文件组( group),每个文件组下至少有2个重做日志文件,如默认的 ib_logfile0和 ib_logfile1。为了得到更高的可靠性,用户可以设置多个的镜像日志组(mirrored log groups),将不同的文件组放在不同的磁盘上,以此提高重做日志的高可用性。在日志组中每个重做日志文件的大小一致,并以循环写入的方式运行。InnoDB存储引擎先写重做日志文件,当达到文件的最后时会切换至重做日志文件2,再当重做日志文件2也被写满时,会再切换到重做日志文件1中。
下图显示了一个拥有3个重做日志文件的重做日志文件组。
下列参数影响着重做日志文件的属性:
- innodb_log_file_size
- innodb_log_files_in_group
- innodb_mirrored_log_groups
- innodb_log_group_home_dir
参数 innodb_log_file_size指定每个重做日志文件的大小。在 InnoDB1.2.x版本之前,重做日志文件总的大小不得大于等于4GB,而1.2.x版本将该限制扩大为了512GB参数 innodb_log_files_in_group指定了日志文件组中重做日志文件的数量,默认为2。
参数innodb _mirrored_log_groups指定了日志镜像文件组的数量,默认为1,表示只有一个日志文件组,没有镜像。若磁盘本身已经做了高可用的方案,如磁盘阵列,那么可以不开启重做日志镜像的功能。最后,参数 innodb_log_group_home_dir指定了日志文件组所在路径,默认为./,表示在 MySQL数据库的数据目录下。
重做日志文件的大小设置对于 InnoDB存储引擎的性能有着非常大的影响。一方面重做日志文件不能设置得太大,如果设置得很大,在恢复时可能需要很长的时间方面又不能设置得太小了,否则可能导致一个事务的日志需要多次切换重做日志文件。
此外,重做日志文件太小会导致频繁地发生 async checkpoint,导致性能的抖动。例如,用户可能会在错误日志中看到如下警告信息:
上面错误集中在 InnoDB: ERROR the age of the last checkpoint is 9433645, InnoDB which exceeds the log group capacity 9433498。这是因为重做日志有一个 capacity变量,该值代表了最后的检查点不能超过这个阈值,如果超过则必须将缓冲池( innodb buffer pool)中脏页列表(flush list)中的部分脏数据页写回磁盘,这时会导致用户线程的阻塞。
也许有人会问,既然同样是记录事务日志,和之前介绍的二进制日志有什么区别?
首先,二进制日志会记录所有与 MySQL数据库有关的日志记录,包括 InnoDB、MyISAM、Heap等其他存储引擎的日志。而 InnoDB存储引擎的重做日志只记录有关该存储引擎本身的事务日志。
其次,记录的内容不同,无论用户将二进制日志文件记录的格式设为 STATEMENT还是ROW,又或者是 MIXED,其记录的都是关于一个事务的具体操作内容,即该日志是逻辑日志。而 InnoDB存储引擎的重做日志文件记录的是关于每个页(Page)的更改的物理情况。
此外,写入的时间也不同,二进制日志文件仅在事务提交前进行提交,即只写磁盘一次,不论这时该事务多大。而在事务进行的过程中,却不断有重做日志条目(redo entry)被写入到重做日志文件中。
在InnoDB存储引擎中,对于各种不同的操作有着不同的重做日志格式。到 InnoDB 1.2.x版本为止,总共定义了51种重做日志类型。虽然各种重做日志的类型不同,但是它们有着基本的格式,下图显示了重做日志条目的结构:
从表可以看到重做日志条目是由4个部分组成:
- redo_log_type占用1字节,表示重做日志的类型
- space表示表空间的ID,但采用压缩的方式,因此占用的空间可能小于4字节
- page_no表示页的偏移量,同样采用压缩的方式
- redo_log_body表示每个重做日志的数据部分,恢复时需要调用相应的函数进行解析
写入重做日志文件的操作不是直接写,而是先写入一个重做日志缓冲(redo log buffer)中,然后按照一定的条件顺序地写入日志文件。下图很好地诠释了重做日志的写入过程。
从重做日志缓冲往磁盘写入时,是按512个字节,也就是一个扇区的大小进行写入。因为扇区是写入的最小单位,因此可以保证写入必定是成功的。因此在重做日志的写入过程中不需要有 doublewrite。
前面提到了从日志缓冲写入磁盘上的重做日志文件是按一定条件进行的,那这些条件有哪些呢?在主线程中每秒会将重做日志缓冲写入磁盘的重做日志文件中,不论事务是否已经提交。另一个触发写磁盘的过程是由参数 innodb_flush_log_at_trx_commit控制,表示在提交(commit)操作时,处理重做日志的方式。
参数 innodb_flush_log_at_trx_commit的有效值有0、1、2。0代表当提事务时够并不将事务的重做日志写入磁盘上的日志文件,而是等待主线程每秒的刷新。1和2不同的地方在于:1表示在执行comm时将重做日志缓冲同步写到磁盘,即伴有 fsync的调用。2表示将重做日志异步写到磁盘,即写到文件系统的缓存中。因此不能完全保证在执行 commit时肯定会写入重做日志文件,只是有这个动作发生。
因此为了保证事务的ACID中的持久性,必须将 innodb_flush_log_ at_trx_commit设置为1,也就是每当有事务提交时,就必须确保事务都已经写入重做日志文件。那么当数据库因为意外发生宕机时,可以通过重做日志文件恢复,并保证可以恢复已经提交的事务。而将重做日志文件设置为0或2,都有可能发生恢复时部分事务的丢失。不同之处在于,设置为2时,当 MySQL数据库发生宕机而操作系统及服务器并没有发生宕机时,由于此时未写入磁盘的事务日志保存在文件系统缓存中,当恢复时同样能保证数据不丢失。