目录
由于InnoDB在MySQL数据库中占有十分重要的地位,并且其实现事务的过程也十分复杂,涉及的面比较广,如果没有一定的知识背景很难理解它的设计思路。因此要想理清InnoDB事务实现的原理,我们很有必要了解一下MySQL的相关知识。
MySQL的逻辑架构
从图1可以看出MySQL有以下几部分组成
连接池组件
管理服务和工具组件
SQL接口组件
查询分析器组件
优化器组件
缓冲组件
插件式组件
物理文件
MySQL数据库区别于其他数据库的最重要的一个特点就是它的插件式存储引擎。MySQL插件式的存储引擎提供了一系列标准的管理和服务支持,这些标准与存储引擎本身无关。需要特别注意的是,存储引擎是基于表的,而不是数据库。
InnoDB存储引擎支持事务,其设计目标主要面向在线事务处理(OLTP)的应用。它的特点是行锁设计,支持外键,并支持非锁定读,即默认读取操作不会产生锁。从MySQL数据库5.5.8版本开始,InnoDB存储引擎是MySQL默认的存储引擎。
InnoDB存储引擎将数据放在一个逻辑的表空间中,这个表空间由InnoDB存储引擎自身进行管理。它可以将每个InnoDB存储引擎的表单独存放到一个独立的ibd文件中,因此它也支持裸设备用来建立它的表空间。
InnoDB通过使用多版本并发控制(MVCC)来获得高并发性,并且实现了SQL标准的4种隔离级别,默认是Repeatable Read级别。同时,使用一种被称为next-key locking的策略来避免幻读(phantom)现象的产生。除此之外,InnoDB存储引擎还提供了插入缓存(insert buffer),二次写(double wrote),自适应哈希索引(adaptive hash index),预读(read ahead)等高性能高可用的功能。
对于表中数据的存储,InnoDB存储引擎采用了聚集(clustered)的方式,因此每张表的存储引擎会为每一行生成6字节的ROWID,并以此作为主键。
InnoDB体系架构
从图2中可以简单看出InnoDB存储引擎的体系结构,InnoDB有多个内存块,可以认为这些内存块组成了一个内存池,负责如下工作:
维护所有进程/线程需要访问的多个内部数据结构。
缓存磁盘上的数据,方便快速地读取,同时在对磁盘文件的数据修改之前在这里缓存。
重做日志(redo log)缓冲
... ...
后台线程的主要作用是负责刷新内存池中的数据,保证缓冲池中的内存缓存是最近的数据,此外将已经修改的数据文件刷新到磁盘文件,同时保证在数据库发生异常的情况下InnoDB能恢复到正常运行状态。
后台线程
InnoDB存储引擎是多线程模型,因此其后台有多个不同的线程,负责处理不同的任务。
Master Thread:是一个非常核心的后台线程,主要负责将缓冲池中异步数据刷新到磁盘,保证数据一致性,包括脏页的刷新,合并插入缓存(insert buffer),undo页的回收。它具有最高的线程优先级别,其内部有多个循环(loop)组成:主循环(loop),后台循环(background loop),刷新循环(flsuh loop),暂停循环(suspend loop)。Master Thread会根据数据库运行状态在这几个循环进行切换。
Loop被称为主循环,因为大多数操作是在这个循环中,其中有两个操作,伪代码如下:
void master_thread{
goto loop;
loop;
for(int i = 0; i < 10; i++){
thread_sleep(1)
do log buffer flush to disk
if(last_one_second_ios < 5)//前一秒IO次数<5
do merge at most 5 insert buffer
//当前缓冲池中脏页的比例是否超过了配置文件中的阈值,默认90%
if(buf_get_modified_ratio_pct > innodb_max_dirty_page_pct)
do buffer pool flush 100 dirty page//将100个脏页写入磁盘
if(no user activity)
goto background loop
}
if(last_ten_seconds_ios < 200)//判断过去10秒的IO操作是否小于200
do buffer pool flush 100 dirty page//小于200InnoDB认为当前有足够的磁盘IO操作能力
do merge at most 5 insert buffer
do log buffer flush to disk
do full purge//删除无用的undo页
if(buf_get_modify_ratio_pct > 70%)//如果有超过70%的脏页比例
do buffer pool flush 100 dirty page //刷新100个脏页到磁盘
else
buffer pool flush 10 dirty page//小于70%,刷新10个脏页到磁盘
goto loop:
background loop:
do full purge//删除无用的undo页
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
suspend loop:
suspend_thread()
waiting event
goto loop;
}
从代码中可以看到,loop循环是通过thread sleep来实现的,这意味着所谓每秒一次或每10秒一次的操作是不精确的。在负载很大的情况会有延迟,只能说大概在这个频率。
每秒一次的操作包括:
日志缓冲刷新到磁盘,即使这个事务还没有提交(总是);
合并插入缓存(可能);
之多刷新100个InnoDB的缓冲池中的脏页到磁盘;
如果当前没有用户活动,则切换到background loop(可能)。
每10秒的操作包括:
合并至多5个插入缓存(总是)
将日志缓存刷新到磁盘(总是)
删除无用的Undo页(总是)
刷新100个或者10个脏页到磁盘(总是)