上图详细展示了InnoDB存储引擎的体系结构,从途中可见,InnoDB 存储引擎由 内存池、后台线程和磁盘文件三大部分组成。
1. Buffer Pool缓冲池
InnoDB存储引擎是基于磁盘存储的,并将其中的记录按照页的方式进行管理。但是由于CPU速度和磁盘速度之间的鸿沟,基于磁盘的数据库系统通常使用缓冲池记录来提高数据库的的整体性能。
在数据库中进行读取操作,将从磁盘中读到的页放在缓冲池中,下次再读相同的页中时,首先判断该页是否在缓冲池中。若在缓冲池中,称该页在缓冲池中被命中,直接读取该页。否则,读取磁盘上的页。
对于数据库中页的修改操作,则首先修改在缓冲池中的页,然后再以一定的频率刷新到磁盘上。页从缓冲池刷新回磁盘的操作并不是在每次页发生更新时触发,而是通过一种称为CheckPoint的机制刷新回磁盘。
所以,缓冲池的大小直接影响着数据库的整体性能,可以通过配置参数 innodb_buffer_pool_size
来设置。
具体来看,缓冲池中缓存的数据页类型有:索引页、数据页、undo页、插入缓冲(insert buffer)、自适应哈希索引 (adaptive hash index)、InnoDB存储的锁信息(lock info)和数据字典信息(data dictionary)。
在架构图上可以看到,InnoDB存储引擎的内存区域除了有缓冲池之外,还有重做日志缓冲和额外内存池。InnoDB 存储引擎首先将重做日志信息先放到这个缓冲区中,然后按照一定频率将其刷新到重做日志文件中。重做日志缓冲一般不需要设置的很大,该值可由配置参数 innodb_log_buffer_size
控制。
1. 数据页和索引页
Page是Innodb存储的最基本结构,也是Innodb磁盘管理的最小单位,与数据库相关的所有内容都存储在Page结构里。Page分为几种类型,数据页和索引页就是其中最为重要的两种类型。
2. 插入缓冲(Insert Buffer)
在InnoDB引擎上进行插入操作时,一般需要按照主键顺序进行插入,这样才能获得较高的插入性能。 当一张表中存在非聚簇的且不唯一的索引时,在插入时,数据页的存放还是按照主键进行顺序存放,但是对于非聚簇索引叶节点的插入不再是顺序的了,这时就需要离散的访问非聚簇索引页,由于随机读取的存在导致插入操作性能下降。
InnoDB为此设计了Insert Buffer来进行插入优化。对于非聚簇索引的插入或者更新操作,不是每一次都直接插入到索引页中,而是先判断插入的非聚集索引是否在缓冲池中,若在,则直接插入;若不在,则先放入到一个Insert Buffer中。看似数据库这个非聚集的索引已经插到叶节点,而实际没有,这时存放在另外一个位置。然后再以一定的频率和情况进行Insert Buffer和非聚簇索引页子节点的合并操作。这时通常能够将多个插入合并到一个操作中,这样就大大提高了对于非聚簇索引的插入性能。
3. 自适应哈希索引(Adaptive Hash Index)
InnoDB会根据访问的频率和模式,为热点页建立哈希索引,来提高查询效率。InnoDB存储引擎会监控对表上各个索引页的查询,如果观察到建立哈希索引可以带来速度上的提升,则建立哈希索引,所以叫做自适应哈希索引。
自适应哈希索引是通过缓冲池的B+树页构建而来,因此建立速度很快,而且不需要对整张数据表建立哈希索引。其有一个要求,即对这个页的连续访问模式必须是一样的,也就是说其查询的条件(WHERE)必须完全一样,而且必须是 连续的。
4. 锁信息(lock info)
我们都知道,InnoDB存储引擎会在行级别上对表数据进行上锁。不过InnoDB也会在数据库内部其他很多地方使用 锁,从而允许对多种不同资源提供并发访问。数据库系统使用锁是为了支持对共享资源进行并发访问,提供数据的完整性和一致性。
5. 数据字典信息(Data Dictionary)
InnoDB有自己的表缓存,可以称为表定义缓存或者数据字典。当InnoDB打开一张表,就增加一个对应的对象到数据字典。
**数据字典是对数据库中的数据、库对象、表对象等的元信息的集合。**在MySQL中,数据字典信息内容就包括表结 构、数据库名或表名、字段的数据类型、视图、索引、表字段信息、存储过程、触发器等内容。MySQL INFORMATION_SCHEMA库提供了对数据局元数据、统计信息、以及有关MySQL server的访问信息(例如:数据库 名或表名,字段的数据类型和访问权限等)。该库中保存的信息也可以称为MySQL的数据字典。
2. Redo log Buffer重做日志缓冲
当缓冲池中的页的版本比磁盘要新时,数据库需要将新版本的页从缓冲池刷新到磁盘。但是如果每次一个页发送变化,就进行刷新,那么性能消耗是非常大的,于是InnoDB采用了**Write Ahead Log(预写日志)**策略,即当事务提交时,先写重做日志,然后再择时将脏页写入磁盘。如果发生宕机导致数据丢失,就通过重做日志进行数据恢复。
**InnoDB存储引擎首先会将重做日志信息放入重做日志缓冲中,然后再按照一定频率将其刷新到重做日志文件。**重做日志缓冲一般不需要设置得很大,因为一般情况每一秒钟都会将重做日志缓冲刷新到日志文件中。可通过配置参数 innodb_log_buffer_size
控制,默认为8MB。
除了每秒刷新机制之外,每次事务提交时重做日志缓冲也会刷新到日志中。InnoDB是事务的存储引擎,其通过 Force Log at Commit
机制实现事务的持久性,即当事务提交时,必须先将该事务的所有日志写入到重做日志文件进行持久化,然后事务的提交操作才算完成。
为了确保每次日志都写入到重做日志文件,在每次将重做日志缓冲写入重做日志后,必须调用一次fsync操作,将缓冲文件从文件系统缓存中真正写入磁盘。
可以通过 innodb_flush_log_at_trx_commit
来控制重做日志刷新到磁盘的策略。该参数默认值为1,表示事务提交必须进行一次fsync操作,还可以设置为0和2。0表示事务提交时不进行写入重做日志操作,该操作只在主线程中完 成,2表示提交时写入重做日志,但是只写入文件系统缓存,不进行fsync操作。由此可见,设置为0时,性能最高, 但是丧失了事务的一致性。
3. Double Write双写
如果说Insert Buffer给InnoDB存储引擎带来了性能上的提升,那么Double Write带给InnoDB存储引擎的是数据页的可靠性。
如上图所示,**Double Write由两部分组成,一部分是内存中的double write buffer,大小为2MB,另一部分是物理磁盘上共享表空间连续的128个页,大小也为2MB。**在对缓冲池的脏页进行刷新时,并不直接写磁盘,而是通过 memcpy函数将脏页先复制到内存中的double write buffer区域,之后通过double write buffer再分两次,每次1MB 顺序地写入共享表空间的物理磁盘上,然后马上调用fsync函数,同步磁盘,避免操作系统缓冲写带来的问题。在完成double write页的写入后,再将double wirite buffer中的页写入各个表空间文件中。
如果操作系统在将页写入磁盘的过程中发生了崩溃,在恢复过程中,InnoDB存储引擎可以从共享表空间中的double write中找到该页的一个副本,将其复制到表空间文件中,再应用重做日志。
4. InnoDB磁盘文件
InnoDB的主要的磁盘文件主要分为三大块:**一是系统表空间,二是用户表空间,三是redo日志文件和归档文件。**二进制文件(binlog)等文件是MySQL Server层维护的文件,所以未列入InnoDB的磁盘文件中。
1. 系统表空间和用户表空间
**InnoDB系统表空间包含InnoDB数据字典(元数据以及相关对象)、double write buffer,change buffer,undo logs的存储区域。系统表空间也默认包含任何用户在系统表空间创建的表数据和索引数据。**系统表空间是一个共享的表空间因为它是被多个表共享的。
系统表空间是由一个或者多个数据文件组成。**默认情况下,1个初始大小为10MB,名为ibdata1的系统数据文件在 MySQL的data目录下被创建。**用户可以使用 innodb_data_file_path 对数据文件的大小和数量进行配置。
innodb_data_file_path 的格式如下:
innodb_data_file_path=datafile1[,datafile2]...
用户可以通过多个文件组成一个表空间,同时制定文件的属性:
innodb_data_file_path = /db/ibdata1:1000M;/dr2/db/ibdata2:1000M:autoextend
这里将/db/ibdata1和/dr2/db/ibdata2两个文件组成系统表空间。如果这两个文件位于不同的磁盘上,磁盘的负载可能被平均,因此可以提高数据库的整体性能。两个文件的文件名之后都跟了属性,表示文件ibdata1的大小为 1000MB,文件ibdata2的大小为1000MB,而且用完空间之后可以自动增长(autoextend)。
设置innodb_data_file_path参数之后,所有基于InnoDB存储引擎的表的数据都会记录到该系统表空间中,如果设置了参数innodb_file_per_table,则用户可以将每个基于InnoDB存储引擎的表**产生一个独立的用户表空间。用户表空间的命名规则为:表名.ibd。**通过这种方式,用户不用将所有数据都存放于默认的系统表空间中,但是用户表空间只存储该表的数据、索引和插入缓冲BITMAP等信息,其余信息还是存放在默认的系统表空间中。
2. 重做日志文件和归档文件
默认情况下,在InnoDB存储引擎的数据目录下会有两个名为ib_logfile0和ib_logfile1的文件,这就是InnoDB的重做日志文件(redo log file),它记录了对于InnoDB存储引擎的事务日志。
当InnoDB的数据存储文件发生错误时,重做日志文件就能派上用场。InnoDB存储引擎可以使用重做日志文件将数据恢复为正确状态,以此来保证数据的正确性和完整性。
每个InnoDB存储引擎至少有1个重做日志文件组(group),每个文件组下至少有2个重做日志文件,如默认的 ib_logfile0和ib_logfile1。
为了得到更高的可靠性,用户可以设置多个镜像日志组,将不同的文件组放在不同的磁盘上,以此来提高重做日志的 高可用性。
在日志组中每个重做日志文件的大小一致,并以【循环写入】的方式运行。InnoDB存储引擎先写入重做日志文件 1,当文件被写满时,会切换到重做日志文件2,再当重做日志文件2也被写满时,再切换到重做日志文件1。
用户可以使用innodb_log_file_size
来设置重做日志文件的大小,这对InnoDB存储引擎的性能有着非常大的影响。 如果重做日志文件设置的太大,数据丢失时,恢复时可能需要很长的时间;另一方面,如果设置的太小,重做日志文件太小会导致依据checkpoint的检查需要频繁刷新脏页到磁盘中,导致性能的抖动。
3. 重做日志的落盘机制
**InnoDB对于数据文件和日志文件的刷盘遵守WAL(Write ahead redo log) 和Force-log-at-commit两种规则,二者保证了事务的持久性。**WAL要求数据的变更写入到磁盘前,首先必须将内存中的日志写入到磁盘;Force-log-at-commit要求当一 个事务提交时,所有产生的日志都必须刷新到磁盘上,如果日志刷新成功后,缓冲池中的数据刷新到磁盘前数据库发生了宕机,那么重启时,数据库可以从日志中恢复数据。
如上图所示,InnoDB在缓冲池中变更数据时,会首先将相关变更写入重做日志缓冲中,然后再按时或者当事务提交时写入磁盘,这符合Force-log-at-commit原则;当重做日志写入磁盘后,缓冲池中的变更数据才会依据checkpoint 机制择时写入到磁盘中,这符合WAL原则。
在checkpoint择时机制中,就有重做日志文件写满的判断,所以,如前文所述,如果重做日志文件太小,经常被写 满,就会频繁导致checkpoint将更改的数据写入磁盘,导致性能抖动。
操作系统的文件系统是带有缓存的,当InnoDB向磁盘写入数据时,有可能只是写入到了文件系统的缓存中,没有真 正的“落袋为安”。
InnoDB的innodb_flush_log_at_trx_commit属性可以控制每次事务提交时InnoDB的行为。当属性值为0时,事务 提交时,不会对重做日志进行写入操作,而是等待主线程按时写入;当属性值为1时,事务提交时,会将重做日志写 入文件系统缓存,并且调用文件系统的fsync,将文件系统缓冲中的数据真正写入磁盘存储,确保不会出现数据丢 失;当属性值为2时,事务提交时,也会将日志文件写入文件系统缓存,但是不会调用fsync,而是让文件系统自己去 判断何时将缓存写入磁盘。
日志的刷盘机制如下图所示:
innodb_flush_log_at_commit是InnoDB性能调优的一个基础参数,涉及InnoDB的写入效率和数据安全。当参数值 为0时,写入效率最高,但是数据安全最低;参数值为1时,写入效率最低,但是数据安全最高;参数值为2时,二者 都是中等水平。一般建议将该属性值设置为1,以获得较高的数据安全性,而且也只有设置为1,才能保证事务的持久性。