MySQL深入学习 ——InnoDB存储引擎

1. InnoDB存储引擎体系架构

www.weixiu3721.com
     innoDB的存储引擎主要体系结构如上图所示

    首先是工作线程:默认7个后台线程,分别是4个io thread(insert buffer、log、read、write),1个master thread(优先级最高),1个锁(lock)监控线程,1个错误监控线程。可以通过show engine innodb status来查看。新版本已对默认的read thread和write thread分别增大到4个,可通过show variables like 'innodb_io_thread%'查看。

    每个线程操作的同一个块大内存池:该内存池中会被分为多个区域,分别是缓冲池(buffer pool)、重做日志缓冲池(redo log buffer)以及额外的内存池(additional memory pool)。内存池所负责也就是维护线程需要访问的数据结构、缓存磁盘文件的数据,方便快速读取,同时在对磁盘文件的数据修改之前在这里缓存,以及重做日志缓存等。具体配置可由show variables like 'innodb_buffer_pool_size'、show variables like 'innodb_log_buffer_size'、show variables like 'innodb_additional_mem_pool_size'三条指令查看来查看。

  第三部分则是最基础的InnoDB存储引擎对应的数据库表磁盘文件、日志文件。

2. InnoDB存储引擎的工作线程 
2.1 Master Thread
    该线程是InnoDB的核心后台线程,大部分工作都在这里完成,负责将缓冲池中的数据异步刷新到磁盘,保证数据的一致性,包括脏页的刷新、合并插入缓冲、undo页的回收等。

    Master Thread具有最高的线程优先级别,其内部有多个循环组成,包括主循环(Loop)、后台循环(background loop)、刷新循环(flush loop)以及暂停循环(suspend loop)。主线程会依据数据库运行状态在四个循环中切换。

    1. 主循环Loop
    主循环包括了大多数操作,其中主要分为两部分,分别是每秒进行的操作和每10秒进行的操作。伪代码如下

可以看到,在loop循环内,首先是一个for循环,该循环总共为10次,其中通过线程的sleep方法实现所谓的每秒和每10秒执行一次的操作,这种方式肯定是不精确地,肯定会有延迟现象,最多也就是大概维持在这个频率。

for循环内的操作(也就是每秒一次的操作)包括:

(1)日志缓冲刷新到磁盘(即使事务没有提交,一定会发生)

(2)合并插入缓冲(不一定)

(3)至多刷新100个InnoDB缓冲池中的脏页到磁盘(可能发生)

(4)判断当前是否有用户活动,如果没有则切换到后台循环执行。

for循环外的操作(每10秒一次执行的操作)包括:

(1)刷新100个脏页的数据到磁盘文件中(可能)

(2)合并至多5个插入缓冲(一定)

(3)将日志缓冲刷新到磁盘中(一定)

(4)删除无用的undo页(一定)

(5)刷新100个或者10个脏页到磁盘文件中(一定)

    2. 后台循环(background)
    若当前没有用户活动,也就是没有客户端连接或者用户登陆时,就会切换到这个循环,主要执行操作如下:

(1)删除无用的undo页(一定)

(2)合并20个插入缓冲(一定)
(3)跳回到主循环(一定)

(4)不断刷新100个页知道符合条件(可能,跳转到flush loop中完成)
如果flush loop中也没有什么事情可做了,InnoDB就会切换到suspend loop中,将主线程挂起,等待时间的发生,如果用户启用了InnoDB引擎,却没有任何基于InnoDB的数据库表,那么主线程将总是处于挂起状态。

2.2 IO Thread
    在MySQL中,InnoDB使用了大量的AIO来处理写IO请求,这样可以很大的提高数据库性能,而IO Thread的任务就是负责处理这些IO请求的回调(AIO基于回调来实现异步加非阻塞),主要有四类IO Thread,分别是write、read、insert buffer和log。可通过innodb_read_io_threads和innodb_write_io_threads参数设置。

2.3 Purge Thread
    事务被提交后,其所使用的undolog可能不再需要,因此需要purge Thread来回收已经使用并分配的undo页,该线程只能存在1个,在innoDB1.1版本之前原本该线程的任务是放在主线程中执行的,但是从1.1版本开始,purge操作有独立线程执行完成。

3. InnoDB引擎的内存结构


3.1 缓冲池buffer pool
    缓冲池,实际上就是一块内存区域,由于磁盘的读写性能相较于CPU的处理速度来说相差较大,所以会通过内存(缓冲池)来提高数据库的整体性能,数据库中读取页中的的数据时,会首先将数据读取到缓冲池中,下次再读取这个页中的数据时,就会直接从缓冲池中获取,如果缓冲池中没有这部分数据,那么就去读取磁盘数据文件,缓存到缓冲池中;对于数据库中页的修改操作,首先是修改在缓冲池中的页,然后再以一定的频率将数据同步更新到磁盘上。

    缓冲池中缓存的数据主要包括:索引页、数据页、undo页、插入缓冲、自适应哈希索引、InnoDB存储的锁信息、数据字典信息等。其中的索引页和数据页占据较大部分。

    缓冲池允许有多个实例,每个页根据哈希值平均分配到不同的缓冲池实例中,这样做的好处是减少数据库内部的资源竞争,增加数据库的并发处理量。可以通过innodb_buffer_pool_instances来查看和配置,该值默认为1,还可以通过innodb_buffer_pool_stats表来查询各个缓冲池的状态。

    LRU List、Free List和Flush List

    缓冲池是一个很大的内存区域,其中存放各种类型页的数据,但是,内存区域是有限的,通常来说,我们不可能把所有的磁盘数据文件全部缓存到内存中,而是通过各种策略来管理内存中的数据。数据库缓冲池是通过LRU(最近最少使用)算法来进行管理的,在innoDB引擎中,缓冲池中页的大小默认为16kb,同样使用LRU算法对缓冲池进行管理,但对LRU算法进行了一些修改优化。

   在innoDB的存储引擎中,LRU列表中还加入了midpoint位置,新读取到得页,虽然是最新读取的数据,但不会放在LRU队列最前端,而是会放在midpoint的位置,默认配置下,midpoint被设置为队列长度的5/8处,但可以通过innodb_old_blocks_pct控制。在midpoint之后的的队列元素就是old队列,之前的就是new队列。

    之所以采用midpoint,而不是直接放在队列首部,是因为某些SQL操作可能会使缓冲池中的热点数据被挤出去,常见的这类操作为索引或者数据的扫描操作,这类操作会访问表中的许多页,甚至是全部的页,而这些页又仅仅只是本次操作中需要访问操作的,并不能算是热点数据,但终归还是要扫描到缓冲区中的,如果放在缓冲区队列首部,那么就可能会把大量的真正热点数据挤出去,导致这些热点数据在下次读取时又需要从磁盘中重新读取,严重影响性能。

    所以,innoDB用了两个参数来解决上面这个问题,除了midpoint外,还有另一个innodb_old_blocks_time,用于表示页读取到mid位置后需要多久才加入到LRU队列的热点数据端。因此当执行上述所说的SQL操作时,可以通过下面的方法尽可能使LRU列表中的热点数据不被刷出。

    首先,新读取的数据肯定会被放在mid位置上,如果该数据在mid位置之后(也就是old部分)存活的次数超过innodb_old_blocks_time次,那么该数据就会放在new队列端(这个操作就是Pages made young),而如果因为innodb_old_blocks_time参数的设置导致页数据并没有从old移动到new队列,该操作被称为Pages not  made young,这样就可以保证mid位置的前面作为热点数据尽量保持其存活,而非热点数据就会处于mid位置的后面,快速被淘汰。   

    通过show engine innodb status命令可以查看内存使用情况(该命令显示的肯定不是实时状态,而是过去的某个时间范围内的状态,比如前60秒内的状态):

    其中,Total large memory表示分配的最大内存空间,Buffer pool size就表示当前共有多少个页的数据,大小为512*16kb,而Free buffers表示Free 队列中页的数量,Database pages 表示LRU队列中页的数量。可能会存在Free buffers+Database pages不等于Buffer pool size,因为Buffer pool size中可能还会有自适应哈希索引、Lock信息等数据,这些数据不用LRU算法维护,因此不会存在于LRU队列中。

    Pages made young 0, not young 0:记录了LRU列表中页移动到new端的次数。

    0.00 youngs/s, 0.00 non-youngs/s:表示每秒这两类操作发生的次数。

    还有另一个非常重要的观察参数buffer pool hit rate,该参数表示缓存命中率,如果命中率为100%,则表示该缓冲池非常优秀,如果小于95%,那么就必须检查是否是由于全表扫描引起的LRU列表被污染的问题。

关于unzip_LRU

   在上图中的数据中,我们可以看到倒数第二行两个参数LRU len:256,unzip_LRU len:0,这两个参数分别表示由LRU管理的总页数,而在innoDB引擎从1.0版本之后支持压缩页的功能,会将原本16kb的页压缩为1kb、2kb、4kb和8kb,而这部分压缩后的页则是由unzip_LRU进行管理的,但是LRU len的数量是包含unzip_LRU的数量的。

关于Flush List

    当LRU中的某个页的数据被修改后,那么该页就被称为脏页,即缓冲池中该页的数据与磁盘上页的数据不一致,而这部分脏页就会复制一份于Flush List中,这时数据库会通过checkpoint机制将脏页的数据刷新到磁盘上,也就是说LRU List中和Flush LIst中各有一份脏页,LRU中保证该页的缓冲可用性,而Flush List则是用来讲脏页数据刷新到磁盘,两者互不影响。在上图的Modified db pages数据就是脏页的数量。

在innodb中定义三种page(页):

1) free page :此page未被使用(通常都是数据库刚刚启动的时候),此种类型page位于free List中,一旦某个页的数据被读取或者操作,那么该页就会从free List中移除,移动到LRU List中

2) clean page:此page被使用,对应数据文件中的一个页面,但是页面没有被修改(仅做读取操作),此种类型 page位于lru List中

3) dirty page:此page被使用,对应数据文件中的一个页面,但是页中的数据被修改过,此种类型page位于lru List和flush List中

3.2 重做日志缓冲(redo log buffer)
    重做日志缓冲,或者说日志缓冲,innoDB引擎首先将重做日志信息存放到这个缓冲区中,然后将按一定频率区刷新到重做日志文件中,重做日志缓冲区一般不需要设置到很大,因为一般都会每秒刷新一次日志缓冲区数据到日志文件中,因此只需要保证每秒产生的事务量在这个缓冲大小之内即可。该值由参数innodb_log_buffer_size控制,默认为8mb。

猜你喜欢

转载自www.cnblogs.com/zxc159/p/12133934.html