由于Mysql数据库中的存储引擎是插件式的,而存储引擎又是基于表的,因此可以根据不同的需要使用不同的存储引擎创建表。
InnoDB是Mysql默认的存储引擎,基于从磁盘存储,其特点是行锁设计、支持MVCC、支持外键、提供一致性非锁定读,同时被设计用来最有效的利用以及使用内存和CPU。
1. InnoDB体系结构
1.1 后台线程
Master Thread(InnoDB 1.0.x版本之前)
Master Thread是一个非常核心的后台线程,主要负责脏页面的刷新、合并插入缓冲、UNDO页的回收等。Master Thread具有最高的线程优先级别。其内部由多个循环组成:主循环、后台循环、刷新循环、暂停循环。Master Thread会根据数据库运行的状态在loop、background loop、flush loop和suspend loop中进行切换。
在主循环中,有两大部分的操作——每秒钟的操作和每10秒的操作.
每秒一次的操作包括:
-
日志缓冲刷新到磁盘,即使这个事务还没有提交(总是)
-
合并插入缓冲(可能)。
InnoDB会判断当前一秒发生的IO次数是否小于5次,如果小于5次,InnoDB认为当前的IO压力很小,可以执行插入缓冲操作
-
至少刷新100个InnoDB的缓冲池中的脏页到磁盘(可能)。
InnoDB判断当前缓冲池中脏页的比例是否超过了配置文件中innodb_max_dirty_pages_pct这个参数(默认90%),如果超过了则将100个脏页写入磁盘中。
每10秒的操作,包括如下内容:
-
刷新100个脏页到磁盘(可能)
判断过去10秒之内磁盘的IO操作是否小于200次,如果是,将100个脏页刷新到磁盘
-
合并至多5个插入缓冲(总是)
-
将日志缓冲刷新到磁盘(总是)
-
删除无用的Undo页(总是)
进行full purge操作,即删除无用的Undo页。
-
刷新100个或10个脏页到磁盘(总是)
判断缓冲池中脏页的比例(buf_get_modified_radio_pct),如果有超过70%的脏页,则刷新100个脏页到磁盘,如果小于70%,则只需要刷新10%的脏页到磁盘。
若当前没有用户活动或者数据库关闭时,切换到background loop
- 删除无用的Unod页(总是)
- 合并20个插入缓冲(总是);
- 跳回到主循环(总是);
- 不断刷新100个页直到符合条件(可能,跳转到flush loop中完成)
如果flush loop中也没有什么事情可以做了,InnoDB存储引擎会切换到suspend_loop,将Master Thread挂起。
整个Master Thread的伪代码如下所示:
void master_thread(){
goto loop;
loop: //主循环
for(int i=0;i<10;++i){
thread_sleep(1); //每1s执行
do log buffer flush to disk; //将重做日志缓冲写进重做日志文件
if(last_one_second_ios<5)
do merge at most 5 insert buffer; //合并插入缓冲
if(buf_get_modified_ratio_pct>innodb_max_dirty_pages_pct)
do buffer pool flush 100 dirty page; //刷新100个脏页到磁盘
if(no user activity)
goto background loop;
} //每10s执行
if(last_ten_second_ios<200)
do buffer pool flush 100 dirty page; //脏页刷新
do merge at most 5 insert buffer; //合并插入缓冲
do log buffer flush to disk; //将重做日志缓冲写进重做日志文件
do full purge;
if(buf_get_modifed_ratio_pct>70%)
do buffer pool flush 100 dirty page; //刷新脏页
else
buffer pool flush 10 dirty page; //刷新脏页
goto loop;
backgroud loop: //background循环
do full purge;
do merge 20 insert buffer;
if not idle:
goto loop:
else:
goto flush loop;
flush loop: //刷新循环
do buffer pool flush 100 dirty page;
if(buf_get_modified_ratio_pct>innodb_max_dirty_pages_pct)
goto flush loop;
goto suspend loop;
}
IO Thread
在InnoDB存储引擎中大量使用了AIO(Async IO)来处理写IO请求,read_thread和write_thread分别为4个,分别使用innodb_read_io_threads和innodb_write_io_threads参数进行设置。
Purge Thread
事务被提交后,其所使用的的undolog可能不再需要,需要PurgeThread来回收已经使用并分配的undo页。从InnoDB 1.1版本开始,purge操作可以独立到单独的线程中进行。
1.2 内存
缓冲池中缓存的数据页类型有:索引页、数据页、undo页、插入缓冲(insert buffer)、自适应哈希索引、数据字典信息等。
缓冲池是一个很大的内存区与,数据库中的缓冲池是通过LRU算法进行管理的。在InnoDB存储引擎中,缓冲池中页的大小默认为16KB。InnoDB对LRU进行了一些优化,在LRU列表中加入了midpoint位置,新读取到的页不是直接放入到LRU列表的首部,而是放入到LRU列表的midpoint位置。midpoint之前的是new列表,midpoint之后的列表是old列表。Innodb_old_blocks_time用于表示页读取到mid位置后需要等待多久才会被加入到LRU列表的热端。
在数据库刚启动时,LRU列表是空的,这时页都存放在Free列表中,需要从缓冲池中分页时,首先从Free列表中查找是否有可用的空闲页,若有则将该页从Free列表中删除,放入到LRU列表中。否则,根据LRU算法,淘汰LRU列表末尾的页,将该内存空间分配给新的页。
在LRU列表中的页被修改后,称该页为脏页(dirty page),Flush list中的页即为脏页列表,脏页既存在于LRU列表中,也存在与Flush列表中。脏页既存在于LRU列表中,也存在与Flush列表中。
InnoDB存储引擎的内存区域除了有缓冲池外,还有重做日志缓冲(redo log buffer)。InnoDB存储引擎首先将日志信息先放入到缓冲区,然后按一定频率将其刷新到重做日志文件。
2. CheckPoint技术
如果重做日志可以无限地增大,缓冲池也足够大,能够缓冲所有数据库的数据,那么就不需要将缓冲池中页的新版本刷新会磁盘,发生宕机时可以通过重做日志来恢复整个数据库系统中的数据到宕机发生的时刻。
但这两个条件现阶段是难以实现的,因此使用Checkpoint技术来解决不能实现这两个条件所带来的的问题:
- 缩短数据库的恢复时间
- 缓冲池不够用时,将脏页刷新到磁盘
- 重做日志不可用时,刷新脏页
数据库放生宕机时,数据库不需要重做所有的日志,只需要对Checkpoint后的重做日志进行恢复,这缩短了恢复的时间。
当缓冲池不够用时,根据LRU算法,淘汰某页,如果该页是脏页,需要强制执行Checkpoint,将脏页也就是页的新版本刷回磁盘。
重做日志出现不可用的情况是因为数据库操作系统对日志的设计都是循环使用的,并不是让其无限增大的。为了实现重做日志文件的重用,必须的强制产生Checkpoint。
InnoDB存储引擎先写重做日志文件1,当达到文件的最后时,会切换至重做日志文件2,再当重做日志文件2也被写满时,会切换到日志文件3,当日志文件3也被写满时,会重写日志文件1,如此进行循环。
重做日志文件不能设置的太大,如果设置的太大,恢复时可能需要很长的时间;另一方面也不能设置的太小,否则可能导致一个失误的日志需要多次切换重做日志文件。
有两种Checkpoint,分别是
- Sharp Checkpoint
- Fuzzy Checkpoint
Sharp Checkpoint发生在数据库关闭时,将所有的脏页都刷新回磁盘。
Fuzzy Checkpoint只刷新一部分脏页,而不是刷新所有的脏页回磁盘。以下四种情况下,会发生Fuzzy Checkpoint:
- Master Thread Checkpoint Master线程定时清理
- FLUSH_LRU_LIST Checkpoint LRU淘汰页时
- Async/Sync Flush Checkpoint 重做日志文件进行覆盖重用时
- Dirty Page too much Checkpoint 脏页数量太多时
3. InnoDB关键特性
- 插入缓冲
- 两次写
- 自适应哈希索引
- 异步IO
- 刷新邻接页
3.1 插入缓冲
B+树的特性决定了非聚集索引插入的离散性(例如以主键顺序依次插入行,但这些行对于非聚集索引来说是离散的,定位非聚集索引的叶子结点是随机读取),对于非聚集索引的插入或更新操作,不是每一次直接插入到索引页中,而是先判断插入的非聚集索引页是否在缓冲池中,若在,直接插入,若不在,则先放入到一个Insert Buffer对象中,然后再以一定的频率和情况进行Insert Buffer和辅助索引叶子结点的merge操作,这时通常能将多个插入合并到一个操作中,大大提高了对于非聚集索引插入的性能。
3.2 两次写
两次写带给InnoDB存储引擎的是数据页的可靠性。
在对缓冲池的脏页进行刷新时,并不直接写磁盘,而是现将其写入到doublewrite buffer,然后通过doublewrite buffer分两次,每次1MB顺序地写入共享表空间的物理磁盘上的doublewrite中,然后再doublewrite buffer中的内容写到各个表空间文件中。
如果操作系统在将页写入磁盘的过程中发生崩溃,在恢复过程中,InnoDB可以从doublewrite中找到该页的一个副本,将其复制到表空间文件,再应用重做日志进行恢复。
3.3 自适应哈希索引
InnoDB存储引擎会监控对表上各索引页的查询,如果观察到简历哈希索引可以带来速度提升,则简历哈希索引。
3.4 异步IO
Sync IO中,每进行一次IO操作,需要等此次操作结束才能继续接下来的操作,如果要扫描多个索引页,需要执行多次IO操作,那么每扫描一个页并等待其完成后在进行下一次的扫描,这是没有必要的。
用户可以在发出一个IO请求后立即再发出另一个IO请求,当全部IO请求发送完毕后,等待所有的IO操作的完成,这就是AIO。
AIO还可以进行IO Merge操作,例如多个IO需要对同一个页进行访问,AIO底层会将这多个IO合并成一个IO请求。
3.5 刷新邻接页
当刷新一个脏页时,InnoDB存储引擎会检测该页所在的区(物理上连续的几个页)的所有页,如果是脏页,那么一起进行刷新,这样做可以通过AIO将多个IO写入操作合并成一个IO操作。