1> Master Thread
主要负责将缓冲池中的数据异步刷新到磁盘,保证数据一致性、包括脏页的刷新、合并插入缓冲区(Insert Buffer)、Undo页的回收等。
2> IO Thread
主要负责使用Aio的写请求的回调处理。分别分为write(4个)、read(4个)、insert buffer、log io thread等。
3> purge thread
事务被提交之后,其所使用的undolog可能不再需要,因此需要PurgeThread来回收已经使用并分配的undo页。
4> page clearner thread
主要负责脏页的刷新。
1> 缓冲区
Innodb是基于磁盘的存储的,但是由于cpu和磁盘的速度存在巨大的差距,所以通过内存的速度来弥补差距。数据库中对于数据页的读取,都是先读取页到缓冲区中,下一次再读取该相同的页的时候先判断是否在缓冲区中,在的话直接读取该页否则读取磁盘上的页。
对于数据库中页的修改则是先修改缓冲区中的页然后再以一定的频率刷新到磁盘上。页从缓冲区刷新到磁盘不是每次页发生变更的时候触发,而是通过一种checkpoint的机制刷新到磁盘。缓冲区中缓冲的页的类型有:索引页、数据页、undo页、插入缓冲(insert buffer)、自适应哈希索引、innodb存储的锁信息、数据字典信息等。
2> LRU、Free List、Flush List
Innodb中的缓冲区使用LRU算法来管理,最频繁使用的再列表的前面,而最少使用的在列表的后面。当缓冲池中的不能存放新读取到的页的收,则将首先释放LRU中尾端的页。Innodb中默认的缓冲池大小默认为16KB。
3> 重做日志缓冲(redo log buffer)
Innodb首先会把重做日志信息存放到这个缓冲区中,然后按照一定的频率刷新到重做日志文件。以下三种情况会将重做日志缓冲区的数据刷新到重做日志文件中:
1) Master Thread每一秒将重做日志缓冲区刷新到重做日志文件。
2) 每个事务提交的时候。
3)当重做日志缓冲区的空间小于1/2时。
当前的事务数据库系统都采用WAL方式,当事务提交的时候先写重做日志,再修改页。当数据库宕机的时候则可以通过重做日志恢复数据。分为以下的checkpoint方式:
1> Sharp checkpoint
发生在数据库关闭时所有的脏页(被修改的页)都刷新回磁盘
2> fuzzy checkpoint
1)Master Thread checkpoint (由master thread 定时每秒或每十秒刷新到重做日志文件)
2) FLUSH_LRU_LIST checkpoint (由于引擎需要保证LRU列表中有100个左右的空闲页可用)
3) Async/sync checkpoint (由于重做日志文件不可用,此时需要强制将一些页刷新回磁盘)
4) dirty page too much checkpoint
1、insert buffer
对于非聚集索引的插入和更新操作,不是每一次直接插入到到索引页中,而是先判断插入的非聚集索引页是否在缓冲池中,若在则直接插入;若不在则先放入到一个Insert buffer中,然后再以一定的频率和情况进行Insert buffer和辅助索引叶子节点的merge操作,这时通常能将多个插入合并到一个操作中,大大提高了对于非聚集索引的插入性能。
insert buffer的数据结构是一颗B+树,并且放在共享表ibdata1空间中
使用Insert buffer需要满足以下两个条件:
1) 索引是辅助索引
2)索引不是唯一的
当数据库宕机的时候,这时候有大量的insert buffer没有合并到非聚集索引中去,这时候恢复可能会需要很久。
2、change buffer
innodb引擎可以对DML操作-insert、delete、update都进行缓冲分别是insert buffer、delete buffer、purge buffer。change buffer的适用对象还是非唯一的索引。对一条记录进行update对应以下两个过程:
1)将记录标记为删除(delete buffer对应这个阶段)
2)真正将记录删除 (purge buffer对应这个阶段)
3、merge insert buffer
可能发生在:
1) 辅助索引页被读取到缓冲池
2)Insert buffer bitmap页追踪到该辅助索引页已无可用空间时。
3) master thread
3、双写(double write)
double write由两部分组成,一部分是内存中的double write,大小为2MB,另一部分是物理磁盘上共享表空间的128页,即两个区,大小为同样的2MB。在对缓冲区的脏页进行刷新时,并不直接写磁盘,而是会通过memcpy函数将脏页先复制到内存中的doublewrite buffer,之后通过doublewrite buffer再分两次,每次1MB的顺序地写入共享表空间的物理磁盘上,然后马上调用fsync函数,同步磁盘,避免缓存写带来的问题。在找个过程中,因为double write是连续的,因此这个过程是顺序写的整个过程开销并不大。在完成double write页的写入后,再将doublewrite buffer中的页写入各个表空间文件中,此时的写入则是离散的。
4、自适应哈希索引
innnodb存储引擎会监控对表上各索引项页的查询。如果观察到建立hash索引可以带来速度提升则建立哈希索引称为自适应哈希索引(AHI)。AHI要求对于这个页的访问模式必须是一样的,并且对于哈希索引只能等于查询,不能进行范围查询。
5、异步IO(AIO)
异步IO可以进行IO merge 将多个IO合并为一个IO,比如对于相邻页的3个IO操作,则可以merge为一个。
6、刷新邻尽页
当刷新一个页的时候,innodb会检测该页所在区的所有页,如果是脏页,那么一起进行刷新
7、启动、关闭、恢复
1、一致性非锁定读
是指innodb引擎通过行多版本的方式来读取当前执行时间数据库中的数据,如果读取的行正在执行delete和update操作,这时候读取操作不会因此等待行上锁的释放,而是读取行的一个快照数据。该实现通过Undo段实现的,undo用来在事务中回滚数据,读取快照数据是不需要上锁的。在事务隔离级别read commited和repeatable read下,innodb引擎使用非锁定读一致性读,但是在读已提交下是读取被锁定的最新的一份快照数据,可重复读下是读取事务开始时的行数据版本。
2、一致性锁定读
innodb对于select语句支持两种一致性的锁定读:
SELECT ***** FOR UPDATE (对于读取的行加上X锁,其他事务不能加任何锁,但是依旧能读)
SELECT ***** LOCAK IN SHARE MODE (对读取的行加上S锁,其他的事务还可以加S锁,但是不能加X锁)
3、自增长与锁
插入的类型有:
老版本的innnodb中对于每个自增长值的表都有自增长计数器,当对含有自增长计数器的表进行插入操作的时候,这个计数器会被初始化,执行以下语句来得到计数器的值: SELECT MAX(auto_inc_col) FROM t FOR UPDATE; 插入操作会依据这个这个自增长计数器值加一赋值自增长列,为了提高执行效率,锁不是在一个事务中完成后释放的,而是在完成对自增长值插入的SQL语句后立即释放。
其中新的版本新增了轻量级互斥量的自增长实现机制innodb提供了一个参数innodb_autoinc_lock_mode来控制自增长的模式。其中可能的值如下:
4、行锁的三种算法
1) Record Lock:单个行记录上的锁。
2) Gap Lock :间隙锁,锁定一个范围,但不包含记录本身。
3) Next-Key Lock:Gap-Lock + Record Lock,锁定一个范围,并且锁定记录本身,是左开右闭方式,当查询的包含唯一索引时锁会降级为Record Lock。
4) Previous-Key Lock:Gap-Lock + Record Lock,锁定一个范围,并且锁定记录本身,是左闭右开方式。
5、锁问题
1) 脏读
一个事务读取到了另外一个没有提交的事务的数据
2) 不可重复读(phantom problem)
一个事务读取到了另外一个事务提交的数据。在innodb中通过Next-Key Lock算法来避免不可重复读的问题。
3) 丢失更新
一个事务的操作会被另外一个事务的更新操作覆盖,导致数据的不一致。要避免更新丢失的情况需要,需要让事务在丢失更新的情况下的操作变成串行化,而不是并行化操作。
6、死锁
死锁时指两个或者两个以上的事务在执行过程中,因争夺锁资源而造成一种相互等待的情况。解决死锁的方式有:
1) 超时等待
2) 死锁检测。通过等待图(wait for graph )的方式检测死锁的存在。
原子性、一致性(事务将数据库一种状态转变为下一种一致状态)、隔离性(要求每个读写事务在提交前对其他的事务不可见)、持久性(事务一旦提交就是永久性的)
1)扁平事务
所有的操作属于同一层次的,其由BEGIN WORK开始,由COMMIT WORK或ROLLBACK WORK结束其间的操作都是原子的,要么都执行,要么都回滚。
2)带有保存点的扁平事务
允许在事务执行过程中回滚到事务中较早的一个状态。保存点用来通知系统应该记住事务的状态,以便当错误发生的时候,事务能保存当时的状态。
3)链事务
4)嵌套事务
5)分布式事务
原子性、一致性、持久性通过数据库的redo log(保证事务的原子性和持久性)和undo log(保证事务的一致性)完成。
重做日志用来实现事务的持久性,即事务中的D。其由两部分组成:一时内存中的重做日志缓冲(redo log buffer)其是易失的;而实重做日志文件(redo log file)其是持久的。
Innodb是事务的存储引擎,其通过Force Log at Commit机制实现事务的持久性,即当事务提交的时候,必须先将该事务的所有日志写入到重做日志文件中进行持久化,待事务的COMMIT操作完成才算完成。这里的日志指的是重做日志,在Innodb中,由两部分组成,即redo log和undo log。redo log用来保证事务的持久性,undo log用来帮助回滚和MVCC的功能。redo log基本上都是顺序写的,在数据库运行时不需要对redo log进行读取操作,而undo log是需要随机读写的。
为了确保日志都写入重做日志文件中,在每次重做日志缓冲都写入日志文件后,innodb存储引擎都需要调用一次fsync操作。由于重做日志文件打开并没有使用O_DIRECT选项,因此重做日志文件都是先写入文件系统缓存中。为了确保日志写入磁盘,必须进行一次fsync操作。由于fsync的效率取决于磁盘的性能,因此磁盘的性能决定了事务提交的性能,也就是数据库的性能。
Innodb引擎允许用户手工设置非持久的情况发生,以此提高数据库的性能。即当事务提交的时,日志不写入重做日志文件,而是等待一个时间周期后再执行fsync操作,由于并非强制再事务提交后进行一次fsync操作,显然可以提高数据库的性能。但是当数据库发生宕机的时候,由于部分日志没有刷新到磁盘,因此会丢失一段最后一段时间的事务。
参数innodb_flush_log_at_trx_commit用来控制重做日志刷新到磁盘的策略。该参数的默认值为1,表示事务提交时必须进行一次fysnc操作。当为0时表示事务提交时不进行写入重做日志操作,这个操作仅在master thread中完成,而在master thread中每一秒会进行一次重做日志的fsync操作。当为2时表示事务提交时将重做日志写入重做日志文件中,但进写入文件系统的缓存中,不进行fsync操作,当操作系统宕机的时候,重启数据库会丢失未从文件系统刷新到重做日志文件那部分的事务。
LSN是log sequence number的缩写,其代表的是日志序列号。再innodb存储引擎中,LSN占用8字节,并且单调递增。LSN表示的含义有:
1) 重做日志写入的总量.
2) checkpoint的位置.
3) 页的版本.
事务进行回滚操作的时候就需要undo log,因此在数据库进行修改的时候,innodb存储引擎不但会产生redo,还会产生一定量的undo。这样当用户由于执行的事务失败了或者由于ROLLBACK语句的请求回滚,就可以利用undo信息回滚到修改之前的样子。
undo存放在数据库的一个特殊段中,这个段称位undo段。undo段存在于共享表空间内。undo是逻辑日志,因此只是将数据库逻辑地恢复到原来的样子。所有的修改都被逻辑地取消了,但是数据结构和页本身在回滚之后可能大不相同。因此当数据库回滚时,它实际上是左做与之前相反的操作。对于每个insert操作,innodb存储引擎会完成一个delete操作;对于每个delete,innodb会执行一个insert操作;对于每个update操作会执行一个相反的update操作。
除了回滚操作,undo的另外一个作用是MVCC,即在innodb中存储引擎中MVCC中的实现是通过undo log完成。当用户读取一行记录的时候,若该记录被其他事务占用,当前事务可以通过undo读取之前的行版本信息,以此实现非锁定读。同时undo也会产生redo,也就是undo的产生会伴随着redo log的产生,这是因为undo log也需要持久性的保护。
存储管理:
事务在undo log segment分配也写入undo log这个过程同样需要写入重做日志,当事务提交时,innodb会做以下的事情:
1)将undo log放入列表中,以供以后的purge操作
2)判断undo log所在的页能否重用,若可以则分配给下个事务使用
事务提交后并不能马上删除undo log和undo log所在的页。这是因为可能还有其他事务需要通过undo log来得到之前行版本记录。故事务提交时将undo log放入一个链表中,是否可以删除undo log及undo log所在的页由purge线程判断。
Undo log格式:
在innodb中undo log格式分为以下两种:
1) insert undo log
由于insert 操作只对当前事务本身可见对,其他的事务不可见,故该undo log可以在事务提交后直接删除,不需要purge操作。
2) update undo log
update undo log记录的是对delete和update操作产生的undo log对象。该undo log可能需要MVCC机制,因此不能在事务提交的时候就删除。提交时方式undo log链表,等待purge线程进行最后的删除。
Purge操作:
delete和update操作并不能直接删除原有的数据。purge用于最终完成delete和update操作,之所以这样是由于MVCC的缘故,事务不能再提交的时候就马上处理。这时候其他的事务可能在引用这一行。是否可以删除这条记录由purge来进行判断,若该行记录已不被其他任何其他事务引用,那么就可以真正的delete操作。
Group Commit:
指的是再在一次fsync时多个事务日志被写入文件。其过程如下:(1)修改内存中的事务的信息,并将日志写入重做日志缓冲区(2)调用fsync将确保日志都从重做日志缓冲区写入磁盘。
1) Flush阶段,将每个事务的二进制日志写入内存中。
2)Sync阶段,将内存中的二进制日志文件刷新到磁盘,若队列中有多个事务,那么仅一次fsync操作就完成了二进制日志的写入,这就是BLGC
3) commit阶段,leader根据顺序调用了存储引擎层事务的提交,Innodb存储引擎就支持group commit,因此修复了原先由于prepare_commit_mutex导致group commit失效的问题。
内部XA事务:
当事务提交的时候先写binlog,然后再写redo log,并且这个两个文件的写入时原子性的,如果不是则可能会导致主从不一致的情况。事务提交的流程也如下所示:
当事务提交的时候,innodb引擎会先做一个prepare操作,将事务的xid写入,接着进行二进制日志的写入,如果在innodb引擎提交前,MySQL数据库宕机,那么数据库在重启的时候会先检查准备的UXID事务是否已经提交,若没有,则在存储引擎层再提交一次。