InnoDB架构分析@TOC
InnoDB存储引擎的体系架构
上图详细显示了InnoDB存储引擎的体系架构,从图中可见,InnoDB存储引擎由内存池,后台线程和磁盘文件三大部分组成。接下来我们就来简单了解一下内存相关的概念和原理
一、InnoDB磁盘文件
1、系统表空间和用户表空间
(1)系统表空间(共享表空间,因为它是被多个表共享的)
- 1、数据字典(data dictionary):记录数据库相关信息
- 2、double write buffer:解决部分写失败(页断裂)
- 3、insert buffer:内存insert buffer数据,周期写入共享表空间,防止意外宕机
- 4、回滚段(rollback segments)
- 5、undo空间:undo页
- 系统表空间也默认包含任何用户在系统表空间创建的表数据和索引数据。
系统表空间配置解析
- 系统表空间是由一个或者多个数据文件组成。
默认情况下,一个初始大小为10MB,名为ibdata1的系统数据文件在MySQL的data目录下被创建。用户可以使用 innodb_data_file_path 对数据文件的大小和数量进行配置。 - innodb_data_file_path 的格式如下:
innodb_data_file_path=datafile1[,datafile2]...
- 示例(这里将/db/ibdata1和/dr2/db/ibdata2两个文件组成系统表空间):
innodb_data_file_path=/db/ibdata1:1000M;/dr2/db/ibdata2:1000M:autoextend
- 如果这两个文件位于不同的磁盘上,磁盘的负载可能被平均,因此可以提高数据库的整体性能。
两个文件的文件名之后都跟了属性,表示文件ibdata1的大小为1000MB,文件ibdata2的大小为1000MB,而且用完空间之后可以自动增长(autoextend)。
(2)用户表空间(独立表空间)
- 1、每个表的数据和索引都会存在自已的表空间中
- 2、每个表的结构
- 3、undo空间:undo页 (需要设置)
如何使用用户表空间?
- 如果设置了参数innodb_file_per_table,则用户可以将每个基于InnoDB存储引擎的表产生一个独立的用户表空间。用户表空间的命名规则为:表名.ibd。
- 通过这种方式,用户不用将所有数据都存放于默认的系统表空间中。
2、重做日志文件和归档文件
(1)哪些文件是重做日志文件?
- 默认情况下,在InnoDB存储引擎的数据目录下会有两个名为ib_logfile0和ib_logfile1的文件,这就是InnoDB的重做日志文件(redo log file),它记录了对于InnoDB存储引擎的事务日志。
(2)重做日志文件的作用是什么?
- 当InnoDB的数据存储文件发生错误时,重做日志文件就能派上用场。InnoDB存储引擎可以使用重做日志文件将数据恢复为正确状态,以此来保证数据的正确性和完整性。
- 为了得到更高的可靠性,用户可以设置多个镜像日志组,将不同的文件组放在不同的磁盘上,以此来提高重做日志的高可用性。
(3)重做日志文件组是如何写入数据的?
- 每个InnoDB存储引擎至少有1个重做日志文件组(group),每个文件组下至少有2个重做日志文件,如默认的ib_logfile0和ib_logfile1。
- 在日志组中每个重做日志文件的大小一致,并以循环写入的方式运行。
- InnoDB存储引擎先写入重做日志文件1,当文件被写满时,会切换到重做日志文件2,再当重做日志文件2也被写满时,再切换到重做日志文件1。
(4)如何设置重做日志文件大小?
- 用户可以使用innodb_log_file_size来设置重做日志文件的大小,这对InnoDB存储引擎的性能有着非常大的影响。
- 如果重做日志文件设置的太大,数据丢失时,恢复时可能需要很长的时间;
- 另一方面,如果设置的太小,重做日志文件太小会导致依据checkpoint的检查需要频繁刷新脏页到磁盘中,导致性能的抖动
二、InnoDB内存结构
1、Buffer Pool缓冲池
(1)概述
- InnoDB存储引擎是基于磁盘存储的,并将其中的记录按照页的方式进行管理。但是由于CPU速度和磁盘速度之间的鸿沟,基于磁盘的数据库系统通常使用缓冲池记录来提高数据库的的整体性能。
- 所以,缓冲池的大小直接影响着数据库的整体性能,可以通过配置参数 innodb_buffer_pool_size来设置
- 具体来看,缓冲池中缓存的数据页类型有:索引页、数据页、undo页、插入缓冲(insert buffer)、自适应哈希索引(adaptive hash index)、InnoDB存储的锁信息(lock info)和数据字典信息(data dictionary)。
- 在架构图上可以看到,InnoDB存储引擎的内存区域除了有缓冲池之外,还有重做日志缓冲和额外内存池。InnoDB存储引擎首先将重做日志信息先放到这个缓冲区中,然后按照一定频率将其刷新到重做日志文件中。重做日志缓冲一般不需要设置的很大,该值可由配置参数 innodb_log_buffer_size 控制
(2)数据页和索引页
- InnoDB存储引擎工作时,需要以Page页为最小单位去将磁盘中的数据加载到内存中,与数据库相关的所有内容都存储在Page结构里。Page分为几种类型,数据页和索引页就是其中最为重要的两种类型。
(3)插入缓冲
主要针对次要索引的数据插入存在的问题而设计。
- 我们都知道,在InnoDB引擎上进行插入操作时,一般需要按照主键顺序进行插入,这样才能获得较高的插入性能。
- 当一张表中存在次要索引时,在插入时,数据页的存放还是按照主键进行顺序存放,但是对于次要索引叶节点的插入不再是顺序的了,这时就需要离散的访问次要索引页,由于随机读取的存在导致插入操作性能下降。
- InnoDB为此设计了Insert Buffer来进行插入优化。对于次要索引的插入或者更新操作,不是每一次都直接插入到索引页中,而是先判断插入的非主键索引是否在缓冲池中,若在,则直接插入;若不在,则先放入到一个Insert Buffer中。看似数据库这个非主键的索引已经插到叶节点,而实际没有,这时存放在另外一个位置。然后再以一定的频率和情况进行Insert Buffer和非聚簇索引页子节点的合并操作。这时通常能够将多个插入合并到一个操作中,这样就大大提高了对于非聚簇索引的插入性能。
2、内存数据落盘分析
(1)整体思路分析
- InnoDB内存缓冲池中的数据page要完成持久化的话,是通过两个流程来完成的,一个是脏页落盘;一个是预写redo log日志。
- 当缓冲池中的页的版本比磁盘要新时,数据库需要将新版本的页从缓冲池刷新到磁盘。但是如果每次一个页发送变化,就进行刷新,那么性能开发是非常大的,于是InnoDB采用了:
Write AheadLog(WAL)策略和Force Log at Commit机制实现事务级别下数据的持久性。
(2)Redo log Buffer重做日志缓冲
- 重做日志: Redo Log 如果要存储数据则先存储数据的日志 , 一旦内存崩了 则可以从日志找
- 重做日志保证了数据的可靠性,InnoDB采用了Write Ahead Log(WAL预写日志)策略,即当事务提交时,先写重做日志,然后再择时将脏页写入磁盘。如果发生宕机导致数据丢失,就通过重做日志进行数据恢复。
insert into xxxx
commit; Redo Log File 写成功 则Commit; - Redo Log : ib_logfile0 ib_logfile1 默认为8MB。可通过配置参数innodb_log_buffer_size 控 制
- Force Log at Commit(强制日志提交)机制实现事务的持久性:
即当事务提交时,必须先将该事务的所有日志写入到重做日志文件进行持久化,然后事务的提交操作完成才算完成。为了确保每次日志都写入到重做日志文件,在每次将重做日志缓冲写入重做日志后,必须调用一次fsync操作(操作系统),将缓冲文件从文件系统缓存中真正写入磁盘。
innodb_flush_log_at_trx_commit
(3)重做日志的落盘机制
可以通过 innodb_flush_log_at_trx_commit 来控制重做日志刷新到磁盘的策略。该参数默认值为1
- 1,表示事务提交必须进行一次fsync操作,还可以设置为0和2。
- 0表示事务提交时不进行写入重做日志操作,该操作只在主线程中完成。
- 2表示提交时写入重做日志,但是只写入文件系统缓存,不进行fsync操作。
- 由此可见,设置为0时,性能最高,但是丧失了事务的一致性。
(4)脏页落盘
- 在数据库中进行读取操作,将从磁盘中读到的页放在缓冲池中,下次再读相同的页时,首先判断该页是否在缓冲池中。若在缓冲池中,称该页在缓冲池中被命中,直接读取该页。否则,读取磁盘上的页。
- 对于数据库中页的修改操作,则首先修改在缓冲池中的页,然后再以一定的频率刷新到磁盘上。页从缓冲池刷新回磁盘的操作并不是在每次页发生更新时触发,而是通过一种称为CheckPoint的机制刷新回磁盘。
(5)Double Write双写
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中找到该页的一个副本,将其复制到表空间文件中,再应用重做日志。
3、CheckPoint: 检查点
(1)简介
思考一下这个场景:如果重做日志可以无限地增大,同时缓冲池也足够大,那么是不需要将缓冲池中页的新版本刷新回磁盘。因为当发生宕机时,完全可以通过重做日志来恢复整个数据库系统中的数据到宕机发生的时刻。
但是这需要两个前提条件:
- 缓冲池可以缓存数据库中所有的数据
- 重做日志可以无限增大
因此Checkpoint(检查点)技术就诞生了,目的是解决以下几个问题:
- 1、缩短数据库的恢复时间;
当数据库发生宕机时,数据库不需要重做所有的日志,因为Checkpoint之前的页都已经刷新回磁盘。数据库只需对Checkpoint后的重做日志进行恢复,这样就大大缩短了恢复的时间。 - 2、缓冲池不够用时,将脏页刷新到磁盘;
当缓冲池不够用时,根据LRU算法会溢出最近最少使用的页,若此页为脏页,那么需要强制执行Checkpoint,将脏页也就是页的新版本刷回磁盘。 - 3、重做日志不可用时,刷新脏页。
因为当前事务数据库系统对重做日志的设计都是循环使用的,并不是让其无限增大的。重做日志可以被重用的部分是指这些重做日志已经不再需要,当数据库发生宕机时,数据库恢复操作不需要这部分的重做日志,因此这部分就可以被覆盖重用。如果重做日志还需要使用,那么必须强制Checkpoint,将缓冲池中的页至少刷新到当前重做日志的位置。
检查点,表示脏页写入到磁盘的时机,所以检查点也就意味着脏页数据的写入
Checkpoint发生的时间、条件及脏页的选择等都非常复杂。而Checkpoint所做的事情无外乎是将缓冲池中的脏页刷回到磁盘,不同之处在于每次刷新多少页到磁盘,每次从哪里取脏页,以及什么时间触发Checkpoint。
(2)Checkpoint分类
在InnoDB存储引擎内部,有两种Checkpoint,分别为:
Sharp Checkpoint、Fuzzy Checkpoint
- sharp checkpoint:在关闭数据库的时候,将buffer pool中的脏页全部刷新到磁盘中。
- fuzzy checkpoint:数据库正常运行时,在不同的时机,将部分脏页写入磁盘。仅刷新部分脏页到磁盘,也是为了避免一次刷新全部的脏页造成的性能问题。
Fuzzy Checkpoint:
- 1、Master Thread Checkpoint;
- 2、FLUSH_LRU_LIST Checkpoint;
- 3、Async/Sync Flush Checkpoint;
- 4、Dirty Page too much Checkpoint
①Master Thread Checkpoint
- 在Master Thread中,会以每秒或者每10秒一次的频率,将部分脏页从内存中刷新到磁盘,这个过程是异步的。正常的用户线程对数据的操作不会被阻塞。
②FLUSH_LRU_LIST Checkpoint
- FLUSH_LRU_LIST checkpoint是在单独的page cleaner线程中执行的。
- MySQL对缓存的管理是通过buffer pool中的LRU列表实现的,LRU 空闲列表中要保留一定数量的空闲页面,来保证buffer pool中有足够的空闲页面来相应外界对数据库的请求。
- 当这个空间页面数量不足的时候,发生FLUSH_LRU_LIST checkpoint。空闲页的数量由innodb_lru_scan_depth参数表来控制的,因此在空闲列表页面数量少于配置的值的时候,会发生checkpoint,剔除部分LRU列表尾端的页面。
③Async/Sync Flush Checkpoint(异步、同步)
- Async/Sync Flush checkpoint是在单独的page cleaner线程中执行的。
- Async/Sync Flush checkpoint 发生在重做日志不可用的时候,将buffer pool中的一部分脏页刷新到磁盘中,在脏页写入磁盘之后,事物对应的重做日志也就可以释放了。
- 关于redo_log文件的的大小,可以通过 innodb_log_file_size 来配置。
对于是执行Async Flush checkpoint还是Sync Flush checkpoint,由checkpoint_age以及async_water_mark和sync_water_mark来决定。
##即checkpoint_age等于最新的lsn减去已经刷新到磁盘的lsn的值
checkpoint_age = redo_lsn-checkpoint_lsn
async_water_mark = 75%*innodb_log_file_size
sync_water_mark = 90%*innodb_log_file_size
- 当checkpoint_age<sync_water_mark的时候,无需执行Flush checkpoint。也就说,redo_log剩余空间超过25%的时候,无需执行Async/Sync Flush checkpoint。
- 当async_water_mark<checkpoint_age<sync_water_mark的时候,执行Async Flushcheckpoint,也就说,redo log剩余空间不足25%,但是大于10%的时候,执行Async Flushcheckpoint,刷新到满足条件1
- 当checkpoint_age>sync_water_mark的时候,执行sync Flush checkpoint。也就说,redo_log剩余空间不足10%的时候,执行Sync Flush checkpoint,刷新到满足条件1。 在mysql 5.6之后,不管是Async Flush checkpoint还是Sync Flush checkpoint,都不会阻塞用户的查询进程。
④Dirty Page too much
- Dirty Page too much Checkpoint是在Master Thread 线程中每秒一次的频率实现的。
- Dirty Page too much 意味着buffer pool中的脏页过多,执行checkpoint脏页刷入磁盘,保证buffer pool中有足够的可用页面。Dirty Page 由innodb_max_dirty_pages_pct配置,innodb_max_dirty_pages_pct的默认值在
innodb 1.0之前是90%,之后是75%。