上面为innodb体系结构图,这章主要介绍mysql innodb 体系结构。
从以下几个方面介绍:
Innodb版本
innodb后台线程
innodb内存结构
innodb关键特性
介绍这三个方面之前,先说一下innodb前言:
1. 相比于oracle的多进程模型,mysql innodb采用单进程多线程模型。
2. 相比于oracle实例与数据库对应关系为n:1,mysql innodb实例与数据库对应关系为1:1。
查看mysql的进程与线程:
一.Innodb版本
Innodb版本 | Mysql版本 | 功能 |
老版本innodb | 5.1之前 | 支持ACID,行锁设计,MVCC |
Innodb 1.0.x | 5.1.x | 官方称为InnoDB Plugin,继承上版本所有功能,增加compress和dynamic页格式。 |
Innodb 1.1.x | 5.5.x | 继承上版本所有功能,增加linux AIO,多回滚段 |
Innodb 1.2.x | 5.6.x | 继承上版本所有功能,增加了全文索引支持,在线索引添加。 |
二.innodb后台线程
Innodb存储引擎是多线程的模型,从innodb体系结构图可知,后台有多个不同的后台线程,处理不同的任务。
1. Master Thread
Master Thread是一个非常核心的后台进程,主要负责将缓冲池中的数据异步刷新到磁盘,保证数据的一致性,包括ditry page的刷新,insert buffer,undo page的回收等。该线程拥有最高的线程优先级别,其内部由多个loop组成:loop,backgroup loop,flush loop,suspend loop,Master Thread会根据数据库运行状态在这些loop中进行切换。
2. IO Thread
在innodb存储引擎中大量使用了AIO(异步IO)来处理写IO请求,这样可以极大提高数据库的性能。IO Thread主要工作是负责这些IO请求的回调。Innodb1.0版本之前共有4个IO Thread,分别是write,read,insert buffer和log IO Thread(如体系结构图所示)。在linux平台下IO THread的数量不能进行调整,但是在windows平台下,可以通过参数innodb_file_io_threads来增大IO Thread。从innodb 1.0.x版本开始,read thread和write thread分别增大到4个,并且不再使用innodb_file_io_threads参数,而是用innodb_read_io_threads和innodb_write_io_threads参数进行设置。
1. Purge Thread(oracle中会出现ora-01555)
事务被提交后,其所有的undolog可能不再需要,因此需要Purge Thread来回收已经使用并分配的undo页。在innodb 1.1版本之前,purge操作仅在Master Thread中完成,在1.1版本开始后,purge操作可以独立到单独的线程中,以减轻Master Thread的工作,从而提高CPU的使用率以及提升存储引擎的性能。用户可以在数据库配置文件中添加innodb_purge_threads=1来启动独立的Purge Thread。注:innodb 1.1版本只能设置1个独立的Purge Thread,innodb 1.2开始可以设置多个Purge Thread。
2. Page Cleaner Thread
Page Cleaner Thread是在innodb 1.2.x版本中引入的。其作用是将从前版本中脏页的刷新操作都放入到单独的线程中来完成。目的是围了减轻原Master Thread的工作以及对用户查询线程的阻塞,进一步提高innodb存储引擎的性能。
一. innodb内存结构
innodb存储引擎内存结构有以下部分:
buffer pool缓冲池
redo log buffe重做日志缓冲池
additional memory pool额外内存池
1.Innodb buffer pool(缓冲池):
Innodb存储引擎是基于磁盘存储的,并将其中的记录按照页(什么是页:类似于oracle中的块,默认大小为16K)的方式进行管理,因此可将其视为基于磁盘对的数据库系统 ,由于cpu和磁盘之间速度的差距,所以基于磁盘的数据库系统通常使用缓冲池技术来提高数据库的性能,
在数据库中进行读取页的操作,首先将从磁盘读到的页内存放在(FIX)缓冲池中,下一次再读取相同的页时,首先判断该页是否在缓冲池中。若在,则直接读取该页,否则读取磁盘上的页。对于数据库中页的修改操作,则首先修改在缓冲池中的页,然后再以一定的频率刷新到磁盘上。这里需要注意的是,页从缓冲池刷新回磁盘的操作并不是在每次也发生更新时触发,而是通过checkpoint的机制刷新回磁盘。
查看Innodb buffer pool:
Innodb buffer pool中缓存的数据页类型包括:
索引页
数据页
undo页
插入缓冲 insert buffer(缓存二级索引)
自适应哈希索引 adaptive hash index
InnoDB存储的锁信息 lock info(行锁)
数据字典信息 data dictionary (DD)
and so on;
介绍下缓冲池中的三个LIST:
LRU List、Free List和Flush List
LRU LIST:
用来管理已经读取的页,数据库中的缓冲池是通过LRU(LastestRecent Used,最近最少使用)算法来进行管理的。即最频繁使用的页放在LRU列表的前端,而最少使用的页放在LRU列表的尾端。当缓冲池不能存放新读取到的页时,将首先释放LRU列表中尾端的页。
InnoDB中,缓冲池中页的大小默认为16KB。对传统LRU算法进行了优化,在LRU列表中还加入了midpoint位置。新读取到的页,虽然是最新访问的页,但并不是直接放入到LRU首部,而是放入到LRU列表的midpoint位置。默认该位置在LRU列表长度的5/8处,可以由参数innodb_old_blocks_pct控制。
在innodb中把midpoint之后的列表称为old列表,之前为new列表(最活跃的热点数据)。若将读取到的页放入到LRU首部,那么某些SQL操作可能会使用缓冲池中的页被刷新出,从而影响缓冲池的效率。常见的这类操作为索引或数据的扫描操作。这类操作需要访问表中的许多页,甚至是全部的页,而这些页仅在这次查询中需要,并不是热点数据。如果这些页被放入首部,则热点数据会被从列表中移除,下次读取页时,需要从磁盘读取。
FREE LIST:
数据库刚启动时,LRU列表为空,所有页都存放在Free列表中。当需要从缓冲池中分页时,首选从Free列表中查找是否有可用的空闲页,若有,则将该页从free列表中删除,放入到LRU列表中。可通过show engine innodb status来观察LRU列表及free列表的使用情况和运行状态:
Flush List:
LRU列表中的页被修改后,成为脏页(dirty page),即缓冲池中的页和磁盘中的页的数据不一致。数据库通过checkpoint机制将脏页刷新回磁盘,Flush 列表中的页为脏页列表。
2.redo log buffe(重做日志缓冲)
Innodb存储引擎首先将重做日志信息先放入到这个缓冲区,然后按一定频率将其刷新到重做日志文件。重做日志缓冲一般不需要设置的很大,因为一般情况下每一秒钟会将重做日志缓冲刷新到日志文件,因此用户之需要保证每秒产生的事务量在这个缓冲大小之内即可。该值由配置参数innodb_log_buffer_size控制,默认为8M。
在通常情况下,8M的重做日志缓冲池足以满足绝大部分的应用,因为重做日志在下列三种情况下将重做日志缓冲中的内容刷新到外部磁盘的重做日志文件中:
1. Master Thread每一秒将重做日志缓冲刷新到重做日志文件。
2. 每个事务提交时将重做日志刷新到重做日志文件。
3. 当重做日志缓冲池剩余空间小于1/2时,重做日志缓冲池刷新到重做日志文件。
3.additional memory pool(额外的缓冲池)
额外的内存池通常被DBA忽略,他们认为该值并不十分重要。事实恰恰相反,该值同样十分重要。在innodb存储引擎中,对内存的管理是通过一种称为内存堆(heap)的方式进行的。在对一些数据结构本身的内存进行分配时,需要从额外的内存池中进行申请,当该区域内存不足时,会从缓冲池中进行申请。例如分配了缓冲池,但是每个缓冲池中的帧缓冲(frame buffer)还有对应的缓冲控制对象(buffer control block),这些对象记录了一些诸如LRU,锁,等待等信息,而这个对象的内存需要从额外的内存池中申请。因此在申请了很大的innodb缓冲池时,也应考虑相应增大这个值。
一.Innodb关键特性
1.insert buffer
Insert Buffer可能是InnoDB存储引擎关键特性中最令人激动与兴奋的一个功能。不过这个名字可能会让人认为插入缓冲是缓冲池中的一个组成部分。其实不然,InnoDB缓冲池中有Insert Buffer信息固然不错,但是Insert Buffer和数据页一样,也是物理页的一个组成部分。
在InnoDB存储引擎中,主键是行唯一的标识符。通常应用程序中行记录的插入顺序是按照主键递增的顺序进行插入的。因此,插入聚集索引(Primary Key)一般是顺序的,不需要磁盘的随机读取。比如按下列SQL定义表:
CREATE TABLE t (
a INT AUTO_INCREMENT,
b VARCHAR(30),
PRIMARY KEY(a)
);
其中a列是自增长的,若对a列插入NULL值,则由于其具有AUTO_INCREMENT属性,其值会自动增长。同时页中的行记录按a的值进行顺序存放。在一般情况下,不需要随机读取另一个页中的记录。因此,对于这类情况下的插入操作,速度是非常快的。
注意 并不是所有的主键插入都是顺序的。若主键类是UUID这样的类,那么插入和辅助索引一样,同样是随机的。即使主键是自增类型,但是插入的是指定的值,而不是NULL值,那么同样可能导致插入并非连续的情况。
但是不可能每张表上只有一个聚集索引,更多情况下,一张表上有多个非聚集的辅助索引(secondary index)。比如,用户需要按照b这个字段进行查找,并且b这个字段不是唯一的,即表是按如下的SQL语句定义的:
CREATE TABLE t (
a INT AUTO_INCREMENT,
b VARCHAR(30),
PRIMARY KEY(a),
key(b)
);
在这样的情况下产生了一个非聚集的且不是唯一的索引。在进行插入操作时,数据页的存放还是按主键a进行顺序存放的,但是对于非聚集索引叶子节点的插入不再是顺序的了,这时就需要离散地访问非聚集索引页,由于随机读取的存在而导致了插入操作性能下降。当然这并不是这个b字段上索引的错误,而是因为B+树的特性决定了非聚集索引插入的离散性。
需要注意的是,在某些情况下,辅助索引的插入依然是顺序的,或者说是比较顺序的,比如用户购买表中的时间字段。在通常情况下,用户购买时间是一个辅助索引,用来根据时间条件进行查询。但是在插入时却是根据时间的递增而插入的,因此插入也是“较为”顺序的。
InnoDB存储引擎开创性地设计了Insert Buffer,对于非聚集索引的插入或更新操作,不是每一次直接插入到索引页中,而是先判断插入的非聚集索引页是否在缓冲池中,若在,则直接插入;若不在,则先放入到一个Insert Buffer对象中,好似欺骗。数据库这个非聚集的索引已经插到叶子节点,而实际并没有,只是存放在另一个位置。然后再以一定的频率和情况进行Insert Buffer和辅助索引页子节点的merge(合并)操作,这时通常能将多个插入合并到一个操作中(因为在一个索引页中),这就大大提高了对于非聚集索引插入的性能。
然而Insert Buffer的使用需要同时满足以下两个条件:
a.索引是辅助索引(secondary index);
b.索引不是唯一(unique)的。
当满足以上两个条件时,InnoDB存储引擎会使用Insert Buffer,这样就能提高插入操作的性能了。不过考虑这样一种情况:应用程序进行大量的插入操作,这些都涉及了不唯一的非聚集索引,也就是使用了Insert Buffer。若此时MySQL数据库发生了宕机,这时势必有大量的Insert Buffer并没有合并到实际的非聚集索引中去。因此这时恢复可能需要很长的时间,在极端情况下甚至需要几个小时。
辅助索引不能是唯一的,因为在插入缓冲时,数据库并不去查找索引页来判断插入的记录的唯一性。如果去查找肯定又会有离散读取的情况发生,从而导致Insert Buffer失去了意义。
用户可以通过命令SHOW ENGINE INNODB STATUS来查看插入缓冲的信息:
mysql>SHOW ENGINE INNODB STATUS\G;
2.两次写(double write)
insert buffer 带给innodb性能提升,两次写是提高数据页的可靠性。
两次写解决的问题:当数据库宕机时,可能innodb存储引擎正在写入某个页到表中,而这个页只写入了一部分,即发生部分写失效,而重做日志是对物理页的修改记录,如果磁盘上这个页已损坏,用重做日志恢复也没任何意义。(其实redo是没有办法恢复的,因为你的页现在已经不完整了。通过页的头尾checksum可以判断出页损坏)。
这意味着,在修改页前,必须有一个正确的页的副本存在,当写入失效发生时,先通过页的副本还原该页,再进行重做,这就是double write。
Double write由两部分组成,一部分是内存中的 double write buffer。 另一部分是在物理磁盘上的共享表空间中的连续128个页,大小和内存中(2M)一致。对缓冲池中的页进行刷新时,并不直接写磁盘,而是memcpy到double write buffer. 之后通过 double write buffer 分两次,每次顺序写入共享表空间1M数据,然后马上调用fsync同步磁盘。这个写入因为共享表空间的double write页是连续的,因此开销不是很大。而完成 double write 页的写入后,再将double write buffer中的页写入各个表空间则是离散的写入。
如果操作系统在将页写入磁盘的过程中发生了崩溃。那么恢复时则可以从共享表空间中double write buffer页找到该页的副本。将其复制到表空间再应用重做日志。
3.自适应HASH索引
Innodb 存储引擎会监控对表上各索引页的查询,如果观察到建立hash索引可以带来速度的提升。则建立hash索引,称之为自适应hash索引(AHI).
AHI有一个要求,即对这个页的连续访问模式必须是一样的. 例如(a, b)这样的联合索引,启访问模式可以使:
WHERE a = xxx
WHERE a = xxx and b = yyy
访问模式一样就是说查询的条件一样。如果交替执行上述的查询操作。则不会建立AHI。
另外,AHI还要求以同一个模式访问了100次,页通过该模式访问了N次,其中N = 页中记录 * 1/16
4.刷新邻近页
当刷新一个脏页时,Innodb存储引擎会通过检测该页所在区的所有页,如果是脏页,会一起进行刷新。
5.异步IO
Innodb 采用异步IO的方式来处理磁盘操作。
6.Check Point技术
为了避免数据丢失的问题,事物数据库系统一般都采用了write ahead log策略。即事物提交时,先写重做日志,再修改页。
但是重做日志不可能无限增大,缓冲值(未刷新到磁盘的脏页)也不可能无限大。即便真可以无限大,数据库宕机后恢复时间也会很长。所以就需要 Check Point技术,该技术可以解决:
• 缩短数据库恢复的时间;
• 缓冲池不够用时,可以将脏页刷新到磁盘;
• 重做日志不可用时(重做日志都是循环利用的),刷新脏页到磁盘;
数据库宕机后重启时,不需要重做所有的日志了。因为 Check Point之前的页都已经刷新到磁盘,数据库只需对Check Point后的重做日志进行恢复即可。从而大大缩短恢复时间。