InnoDB存储引擎(Mysql)

一、InnoDB架构图

 

 

   上图详细展示了InnoDB存储引擎的存储结构,由图可见,InnoDB存储引擎由内存池、后台线程和磁盘三大部分组成。

二、InnoDB磁盘文件

  InnoDB的磁盘文件分为系统表空间、用户表空间、Redo日志文件和归档文件。

  二进制文件(binlog)是由MySql_Service来维护的文件,因此未列入InnoDB的磁盘文件中。

(一)系统表空间和用户表空间

  1、系统表空间

    存储内容:

      系统表空间是一个共享的表空间,因为他是被多个表共享的,InnoDB共享表空间包含InnoDB数据字典(元数据以及相关对象)、double write buffer、change buffer、undo logs的存储区域。

      系统表空间也默认包含所有用户在系统表空间创建的表数据和索引。

    配置解析:

      系统表空间是由一个或者多个数据文件组成,默认情况下,一个默认10M,名为ibdata1的系统数据文件在Mysql的data目录下被创建,我们可以使用innodb_data_file_path来对数据文件的大小和数量进行配置。

      配置如下:

innodb_data_file_path=/db/ibdata1:1000M;/dr2/db/ibdata2:1000M:autoextend

      如果这两个文件存在不同的磁盘上,磁盘的负载可能会被平均,因此可以提高数据库的性能。两个文件后面都跟了属性,表示每个文件的大小,autoextend表示每个文件空间用完后,可以自动增长。

      

  2、用户表空间

    如果设置了参数innodb_file_per_table,用户可以将每一个基于InnoDB存储引擎的表产生一个独立的用户表空间,用户表空间的命名规则为:tablename.ibd,通过这种方式,用户不用将所有的数据都存放于默认的系统表空间中。

    用户表空间只存储表的数据、索引和插入缓冲BITMAP等信息,其余信息还是存放在默认的系统表空间中。 

      

(二)重做日志文件

  存储内容:默认情况下,在InnoDB存储引擎的目录下,会有两个文件ib_logfile0和ib_logfile1,这就是InnoDB的重做日志文件,他记录了对于InnoDB存储引擎的事务日志。

  作用:(1)当InnoDB的数据存储文件发生错误时,重做日志文件就能派上作用,InnoDB存储引擎可以使用重做日志对数据进行恢复,保证数据的正确性和完整性。(2)为了得到更高的可靠性,用户可以设置多个镜像日志组,将不同文件存放在不同的磁盘上,以此来提高重做日志的高可用性。

  重组日志文件如何写入数据:每个InnoDB存储引擎至少有一个重做日志文件组,每个文件组下至少两个重做日志文件,默认为ib_logfile0和ib_logfile1;在日志组中,每个重做日志文件的大小一样,并以循环写入的方式运行,InnoDB存储引擎先写入重做日志文件1,当被写满时,会切换到重做日志文件2,在当重做日志2也被写满时,再切换到重做日志文件1。

  设置重组文件大小:可以使用innodb_log_file_size来设置重做日志文件的大小,这对InnoDB存储引擎的性能有着非常大的影响。如果重组日志太大,数据丢失时,恢复可能需要很长时间;如果重组日志台小,会导致依据checkpoint的检查需要频繁刷新脏页到磁盘中,导致性能抖动。

      

(三)InnoDB逻辑存储结构

  InnoDB存储引擎逻辑存储结构可分为五个等级:表空间、段、区、页、行

  

  1、表空间

    从InnoDB存储引擎的存储逻辑结构来看,所有数据都被逻辑的放在一个空间中,称之为表空间(tablespace)。

    从功能上看,InnoDB存储引擎的表空间分为系统表空间,独占表空间,通用表空间,临时表空间,Undo表空间。

    如果开启了独立表空间innodb_file_pre_table=1,每张表的数据都会存储到一个独立的表空间,即一个单独的.ibd文件。

    InnoDB存储引擎有一个共享表空间,叫做系统表空间,对一个磁盘上的文件名为ibdata1,如果设置了参数innodb_file_per_table=0,关闭了独占表空间,则所有基于InnoDB存储引擎的表数据都会记录到系统表空间。

  2、段

    表空间是由各个段组成,常见的段有数据段、索引段、回滚段等。

    如果开启了独立表空间innoDB_file_per_table=1,每张表的数据都会存储到一个独立的表空间,即一个单独的.ibd文件。一个用户表空间里由很多段组成,创建一个索引时会创建两个段,索引段和数据段。

    数据段存储着索引树中叶子节点的数据,索引段存储着索引树中非叶子节点的数据。

    一个段的空间大小是随着表的大小自动扩展的,表有多大,段就有多大,一个段会包含多个区,至少会有一个区,段扩展的最小单位是区。

  3、区

    一个区由64个连续的页组成,一个区的大小=1M=64个页(16K),为了保证页中区的连续性,区扩展时InnoDB存储引擎会一次性从磁盘申请4~5个区。

  4、页

    InnoDB每个页默认大小是16K,页是InnoDB管理磁盘的最小单位,也是InnoDB中磁盘和内存交互的最小单位。

mysql> show global variables like 'innodb_page_size';
+------------------+-------+
| Variable_name    | Value |
+------------------+-------+
| innodb_page_size | 16384 |
+------------------+-------+

    索引树上一个节点就是一个页,Mysql规定一个页上最少存储两个数据项,如果向一个页插入数据时,这个页已经满了,就会从区中分配一个新页,如果向索引树叶子节点中间的一个页中插入数据,如果这个页是满的,就会发生页分裂。

    操作系统管理磁盘的最小单位也是页,是操作系统读写磁盘的最小单位,Linux中页一般是4K,可以通过命令查看。

[root@lcl-aliyun ~]# getconf PAGE_SIZE
4096

    所以InnoDB从磁盘中读取一个数据页时,操作系统会分4次从磁盘中读取数据到内存,写入也是一样的,需要分4次从内从写入到磁盘。

  5、行

    InnoDB的数据是以行为单位存储的,1个页中包含多个行,在Mysql5.7中,InnoDB提供了4种行格式:Compact、Redundant、Dynamic、Compressed,Dynamic是Mysql5.7中的默认格式。

三、InnoDB内存结构

(一)Buffer Pool缓冲池

  概述:

    InnoDB存储引擎是基于磁盘存储的,并将其中的记录按照页的方式进行管理,但是由于CPU速度和磁盘速度的差异, 使用磁盘的数据库系统通常会使用缓冲记录来提高数据库的整体性能。所以缓冲池的大小直接影响着数据库的整体性能,可以通过参数innodb_buffer_pool_size来设置。

    具体来看,缓冲池中缓存的数据页类型有:索引页、数据页、插入缓冲、自适应hash索引,InnoDB存储的锁信息和数据字典。

    在架构图上可以看到,InnoDB存储引擎的内存区域除了有缓冲池之外,还有重做日志和额外内存池,InnoDB存储引擎首先将重做日志信息放到该缓冲池中,然后按照一定的频率将其刷新到重做日志文件中。重做日志缓冲区一般不需要太大,该值由innodb_log_buffer_size空指。

  数据页和索引页:

    InnoDB存储引擎工作时,需要以page页为最小单位将磁盘中的数据加载到内存中,与数据库相关的所有内容都存储在page结构中。

    Page分为几种类型,数据页和索引页就是其中重要的两种类型。

  更新缓冲(插入缓冲):

    更新缓冲主要是针对次要索引的数据插入存在的问题而设计的。

    在InnoDB引擎上进行插入时,一般需要按照逐渐顺序进行插入,这样才能获得较高的插入性能,当一种表中存在次要索引时,在插入数据时,数据页的存放仍然还是按照主键进行顺序存放的,但是对于次要索引的叶子节点的插入就不再是顺序的了,这时就需要离散的访问次要索引页,由于随机读取的存在导致插入性能下降。

    InnoDB因此设计了Change Buffer来进行插入优化,对于次要索引的插入或者更新操作,不是每一次都直接插入到索引页中,而是先判断插入的非主键索引是否在缓冲池中,如果在,则直接插入,如果不在,则先放入到一个change buffer中,看似数据库的这个非主键索引已经插到叶子节点,但是实际上是存放在了另一个位置,然后再以一定的频率和情况进行Change Buffer和非聚簇索引叶子节点的合并操作,这是通常能够将多个插入合并到一个操作中,这样就大大提高了对于非聚簇索引的插入性能。

  自适应Hash索引:

    InnoDB会根据访问的频率和模式,为热点页建立哈希索引,来提高查询效率。

    hash查找的时间复杂度为O(1),一般一次就可以定位到数据,而B+树的查找次数,取决于B+树的高度,在生产环境中,一般情况下B+树的高度为3~4,因此需要查找3~4此。

    InnoDB存储引擎会监控对标上个索引页的查询,如果观察到建立哈希索引可以带来速度的提升,则建立哈希索引,称之为自适应哈希索引(AHI),自适应哈希索引是通过缓冲池的B+树页构造而来,因此建立的速度很快,而且不需要对整张表构建哈希索引。InnoDB存储引擎会根据访问的频率和模式来自动的为某些热点页建立哈希索引。

    同时自适应哈希有一个要求,就是对这个页的访问模式必须是一样的,例如查询列必须是一样的,不能一次是a列,一次是a列+b列。

    除此之外,自适应哈希还有两个要求:以该模式访问了100次,页通过该模式访问了N次,其中N=页中的记录/16

    自适应哈希可以通过innodb_adaptive_hash_index来进行配置。

mysql> show variables like 'innodb_adaptive_hash_index';
+----------------------------+-------+
| Variable_name              | Value |
+----------------------------+-------+
| innodb_adaptive_hash_index | ON    |
+----------------------------+-------+

  锁信息:

    InnoDB存储引擎会在行级别上对标数据进行上锁,不过InnoDB也会在数据库内部其他很多地方使用锁,从而允许对多种不同资源提供并发访问,数据库系统使用锁是为了支持对共享资源进行并发访问,提供数据的完整性和一致性。

  数据字典信息:

    InnoDB有自己的表缓存,可以成为表定义缓存或者数据字典,当InnoDB打开一张表,就增加一个对应的对象到数据字典。

    数据字典是对数据库中的数据、库对象、表对象等辕信息的集合,在Mysql中,数据字典信息内容就包括表结构、数据库名、表明、字段的数据类型、视图、索引、表字段信息、存储过程、触发器等内容。

(二)内存数据落盘

  1、整体思路分析

    

 

 

    InnoDB内存缓冲池中的数据页要完成持久化,需要通过两个流程来完成:脏页落盘&预写redo log日志。

    当缓冲池中的页的版本比磁盘要新时,数据库需要将新版本的页从缓冲池刷新到磁盘,但是如果每次一个页发生变化就进行刷新,那么性能损耗也是很大的,于是InnoDB采用了Write Aheah Log(WAL)和Froce Log at Commit机制实现事务级别下数据的持久性。

    WAL要求数据变更写入磁盘前,必须将内存中的日志写入到磁盘,而Force Log at Commit要求当一个事务提交时,所有产生的日志都必须刷新到磁盘上,如果日志刷新成功后,缓冲池中的数据刷新到磁盘前数据库发生宕机,那么重启时,数据库可以从日志中恢复数据。

    为了去报每次日志都写入到重做日志文件,在每次将重做日志缓冲写到重做日志后,都需要调用一次fsync操作,将linux操作系统中的缓冲真正写入到磁盘。

  2、脏页落地

    在数据库中读取数据,将从磁盘中读到的页放在缓冲池下,下次再次读取时,首先判断该页是否存在缓冲池,如果存在,则直接取缓冲池中的页,如果没有,则再读取磁盘上的数据。

    在数据库中修改数据,首先修改缓冲池中的页,然后再以一定的频率刷新到磁盘上,页从缓冲池刷新到磁盘上并不是每次页发生变更时触发,而是通过CheckPoint机制刷新回磁盘。

  3、重做日志落盘

    LogBuffer写入磁盘的时机,由参数innodb_flush_log_at_trx_commit控制,该参数可以设置为0、1、2,默认为1。

    0:Mysql每秒一次将数据从log buffer写入到redo log,如果Mysql崩溃或者父无宕机,此时内存中的数据会全部丢失,最多会丢失1秒的事务数据。

    1:每次事务提交时,Mysql将数据从log buffer写入日志文件并同时fsync刷新到磁盘中;该模式为默认模式,Mysql崩溃时,已提交的数据不会丢失。

    2:每次事务提交时,都会将数据刷新到操作系统的缓冲区,可以认为已经持久化磁盘,如果Mysql崩溃,数据不会丢失,但是如果服务器宕机或者意外断电等情况,会丢失操作系统内缓存的数据。

    只有设置为1时,才可以真正的保证事务的持久性,但是由于Mysql执行刷新操作fsync是阻塞的,直到完成后才会返回,因此Mysql的性能会明显的下降。其实0和2是性能很好的,但是0会丢失数据,2虽然说会丢失服务器宕机会丢失操作系统中缓存的数据,但是发生这种情况的概率是非常低的。

mysql> show variables like 'innodb_flush_log_at_trx_commit';
+--------------------------------+-------+
| Variable_name                  | Value |
+--------------------------------+-------+
| innodb_flush_log_at_trx_commit | 1     |
+--------------------------------+-------+

(三)CheckPoint检查点机制

  CheckPoint主要就是为了解决以下三个问题:

    (1)缩短数据库的恢复时间(那么重做日志就不能太大)

    (2)缓冲池不够用时,将脏页刷新到磁盘。

    (3)重做日志不可用时,刷新脏页。

  当数据库发生宕机时,舒徐库不需要重做所有日志,因为CheckPoint之前的页已经刷新到磁盘,数据库只需要对CheckPoint之后的重做日志进行恢复,这样就大大的缩短了恢复时间。

  当缓冲池不够用时,根据LRU算法会溢出最近最少使用的页,若此页为脏页,那么需要强制执行CheckPoint,将脏页的新版本刷回到磁盘。

  当重做日志不可用时,因为当前事务数据库对重做日志的设计都是循环使用的,并不是让其无限增大,重做日志可以被重用的部分是指这些重做日志已经不需要使用,如果这些重做日志还需要使用,那么就需要强制CheckPoint,将缓冲池中的数据写入到重做日志中。

  对于InnoDB而言,是通过LSN(Log Sequence Number)来标记版本的。LSN是8字节的数字,每个页有LSN,重做日志也有LSN,可以通过命令show engine innodb status \G来查看。

  CheckPoint发生的时间、条件及脏页的选择都比较复杂,而CheckPoint所作的事无外乎是将缓冲池中的脏页刷回到磁盘,不同之处在于每次刷新多少页到磁盘,每次从哪里取脏页,以及什么时间触发CheckPoint。

  对于CheckPoint分类可以分为四种 Mater Thread CheckPoint、FLUSH_LRU_LIST_CheckPoint、Async/Sync Flush CheckPoint、Dirty Page too much CheckPoint

   Mater Thread CheckPoint:

    在Mater Thread中,会以每秒或者每10秒一次的频率,将部分脏页从内存中刷新到磁盘,这个过程是异步的,正常的用户线程对数据的操作不会被阻塞。

   FLUSH_LRU_LIST_CheckPoint:

    FLUSH_LRU_LIST_Checkpoint是在单独的page_cleaner线程中执行的,Mysql对缓存的管理是通过Buffer pool中的LRU列表实现的,LRU空闲列表中要保留一定数量的空闲页面,来保证buffer pool中有足够的空闲页来响应外界对数据库的请求。

    当这个空间页不足时,会发生FLUSH_LRU_LIST_Checkpoint。

    空闲页的数量由innodb_lru_scan_depth参数来控制,因此在空闲列表页面数量少于配置的值时,会发生checkpoint,提出部分LRU列表尾部的页面。

   Async/Sync Flush CheckPoint:

    该CheckPoint也是在单独的page cleaner线程中执行的,其发生在重做日志不可用时,将buffer pool中的一部分脏页刷新到磁盘中,在脏页写入磁盘后,事务对应的重做日志就可以释放了。

    对于redo log的大小,可以通过innodb_log_file_size来配置。

mysql> show variables like 'innodb_log_file_size';
+----------------------+----------+
| Variable_name        | Value    |
+----------------------+----------+
| innodb_log_file_size | 50331648 |
+----------------------+----------+

    到底是同步还是异步,需要有checkpoint_age以及async_water_mark和sync_water_mark来决定

##即checkpoint_age等于最新的lsn减去已经刷新到磁盘的lsn的值 
checkpoint_age = redo_lsn-checkpoint_lsn
async_water_mark = 75%*innodb_log_file_size
sync_water_mark = 90%*innodb_log_file_size

    1. 当checkpoint_age<sync_water_mark的时候,无需执行Flush checkpoint。也就说,redo log剩余空间超过25%的时候,无需执行Async/Sync Flush checkpoint。

    2. 当async_water_mark<checkpoint_age<sync_water_mark的时候,执行Async Flush checkpoint,也就说,redo log剩余空间不足25%,但是大于10%的时候,执行Async Flush checkpoint,刷新到满足条件1

    3. 当checkpoint_age>sync_water_mark的时候,执行sync Flush checkpoint。也就说,redo log剩余空间不足10%的时候,执行Sync Flush checkpoint,刷新到满足条件1。 在mysql 5.6之后,不管是Async Flush checkpoint还是Sync Flush checkpoint,都不会阻 塞用户的查询进程。

     总结: 由于磁盘是一种相对较慢的存储设备,内存与磁盘的交互是一个相对较慢的过程 由于innodb_log_file_size定义的是一个相对较大的值,正常情况下,由前面两种checkpoint刷新 脏页到磁盘,在前面两种checkpoint刷新脏页到磁盘之后,脏页对应的redo log空间随即释放, 一般不会发生Async/Sync Flush checkpoint。同时也要意识到,为了避免频繁低发生Async/Sync Flush checkpoint,也应该将innodb_log_file_size配置的相对较大一些。

   Dirty Page too much CheckPoint:

    Dirty Page too much CheckPoint是在Master Thread线程中每秒一次的频率写入的,Dirty Page too much意味着buffer pool中的脏页过多,执行checkpoint脏页刷入磁盘,保证buffer pool中有足够的可用页面。

    Dirty Page由innodb_max_dirty_pages_pct配置,默认值在innodb1.0为90%,后面的版本为75%。

mysql> show variables like 'innodb_max_dirty_pages_pct';
+----------------------------+-----------+
| Variable_name              | Value     |
+----------------------------+-----------+
| innodb_max_dirty_pages_pct | 75.000000 |
+----------------------------+-----------+

(四)Double Write双写

  如果说InsertBuffer给InnoDB存储引擎带来了性能上的提升,那么Double Write给InnoDB带来了数据页的可靠性。

 

   如图所示,Double Write由两部分组成,一部分是内存中的double write buffer,大小为2M,另一部分是物理磁盘上共享表空间连续的128个页,大小也为2M.

  在对缓冲池的脏页进行刷新时,并不直接写磁盘,而是通过memcpy函数将脏页先复制到内存中的double write buffer区域,之后通过double write buffer再分两次,每次1M顺序的写入共享表空间的物理磁盘上,然后马上调用fsync函数,同步磁盘,避免操作系统缓冲写带来的问题。在完成double write页的写入后,再将double write buffer中的页写入各个表空间文件中。

  如果操作系统在将页写入磁盘的过程中发生了崩溃,在恢复过程中,InnoDB存储引擎可以从共享表空间中的double write中找到该页的一个副本,将其复制到表空间文件中,再应用重做日志。

(五)Redo log Buffer重做日志缓冲

 

  在checkpoint择时机制中,就有重做日志文件写满的判断,所以,如前文所述,如果重做日志文件太小,经常被写满,就会频繁导致checkpoint将更改的数据写入磁盘,导致性能抖动。

  操作系统的文件系统是带有缓存的,当InnoDB向磁盘写入数据时,有可能只是写入到了文件系统的缓 存中,没有真正的“落袋为安”。

  InnoDB的innodb_flush_log_at_trx_commit属性可以控制每次事务提交时InnoDB的行为。

  当属性值为0时,事务提交时,不会对重做日志进行写入操作,而是等待主线程按时写入每秒写入一次;

  当属性值为1时,事务提交时,会将重做日志写入文件系统缓存,并且调用文件系统的fsync,将文 件系统缓冲中的数据真正写入磁盘存储,确保不会出现数据丢失;

  当属性值为2时,事务提交时,也会将日志文件写入文件系统缓存,但是不会调用fsync,而是让文件系统自己去判断何时将缓存写入磁盘。

  innodb_flush_log_at_commit是InnoDB性能调优的一个基础参数,涉及InnoDB的写入效率和数据安全。当参数值为0时,写入效率最高,但是数据安全最低;参数值为1时,写入效率最低,但是数据安全最高;参数值为2时,二者都是中等水平。一般建议将该属性值设置为1,以获得较高 的数据安全性,而且也只有设置为1,才能保证事务的持久性。

posted @ 2021-03-04 00:14  李聪龙  阅读(436)  评论(0编辑  收藏  举报