0%

mysql02_Innodb存储引擎

体系架构

后台线程

  1. Master Thread 主要负责将缓冲池中的数据异步刷新
    到磁盘,保证数据的一致性,包括脏页的刷新、合并插人缓冲( INSERT BUFFER)、UNDO页的回收等。
  2. IO Thread 在 InnoDB存储引擎中大量使用了AIO( Async IO)来处理写IO请求,这样可以极大提高数据库的性能。而 IO Thread的工作主要是负责这些IO请求的回调( call back)处理。
  3. Purge Thread 事务被提交后,其所使用的 undolog可能不再需要,因此需要 PurgeThread来回收已经使用并分配的undo页。
  4. Page Cleaner Thread 在 InnoDB12x版本中引入的。其作用是将之前版本中脏页的刷新操作都放入到单独的线程中来完成。而其目的是为了减轻原 Master Thread的工作及对于用户査询线程的阻塞,进一步提高 InnoDB存储引擎的性能

内存

缓冲池

InnoDB存储引擎是基于磁盘存储的,并将其中的记录按照页的方式进行管理。

基于磁盘的数据库系统通常使用缓冲池技术来提高数据库的整体性能。

在数据库中进行读取页的操作,首先将从磁盘读到的页存放在缓冲池中,
这个过程称为将页“FIX”在缓冲池中。下一次再读相同的页时,首先判断该页是否在缓冲池中。若在缓冲池中,称该页在缓冲池中被命中,直接读取该页。否则,读取磁盘上的页。

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

缓冲池中缓存的数据页类型有:索引页、数据页、undo页、插入缓冲、自适应哈希索引、 InnoDB存储的锁信息、数据字典信息等。

LRU List、Free List和Flush List

在 InnoDB存储引擎中,缓冲池中页的大小默认为16KB,数据库中的缓冲池是通过LRU算法来进行管理的。即最频繁使用的页在LRU列表的前端,而最少使用的页在LRU列表的尾端。当缓冲池不能存放新读取到的页时,将首先释放LRU列表中尾端的页。

innnodb的LRU优化

InnoDB的存储引擎中,LRU列表中还加入了midpoint位置。新读取到的页,虽然是最新访问的页,但并不是直接放入到LRU列表的首部,而是放入到LRU列表的 midpoint位置。在默认配置下,该位置在LRU列表长度的5/8处

为什么不用朴素LRU

LRU是热点数据在链表前部,如果一次在lru里面添加大量的页,大量数据往LRU的前部插入,把原来的热点数据冲到链表外面,导致下次需要从磁盘读取。

在LRU列表中的页被修改后,称该页为脏页( dirty page),即缓冲池中的页和磁盘上的页的数据产生了不一致。这时数据库会通过 CHECKPOINT机制将脏页刷新回磁盘,而 Flush List中的页即为脏页列表。需要注意的是,脏页既存在于LRU列表中,也存在于Fush列表中。LRU列表用来管理缓冲池中页的可用性, Flush列表用来管理将页刷新回磁盘,二者互不影响。

redo log buffer

InnoDB存储引擎首先将重做日志信息先放入到这个缓冲区,然后按一定频率将其刷新到重做日志文件。

下列三种情况下会将重做日志缓冲中的内容刷新到外部磁盘的重做日志文件中:

  1. Master Thread每一秒将重做日志缓冲刷新到重做日志文件
  2. 每个事务提交时会将重做日志缓冲刷新到重做日志文件;
  3. 当重做日志缓冲池剩余空间小于1/2时,重做日志缓冲刷新到重做日志文件。

缓存池设计目标

缓冲池的设计目的为了协调CPU速度与磁盘速度的鸿沟。因此页的操作首先都是在缓冲池中完成的。如果一条DML语句,如 Update或 Delete改变了页中的记录,那么此时页是脏的,即缓冲池中的页的版本要比磁盘的新。

数据库需要将新版本的页从缓冲池刷新到磁盘。

倘若每次一个页发生变化,就将新页的版本刷新到磁盘,那么这个开销是非常大的。若热点数据集中在某几个页中,那么数据库的性能将变得非常差。同时,如果在从缓冲池将页的新版本刷新到磁盘时发生了宕机,那么数据就不能恢复了。

为了避免发生数据丢失的问题,当前事务数据库系统普遍都采用了 Write Ahead log策略,即**当事务提交时,先写重做日志,再修改页。**当由于发生宕机而导致数据丢失时,通过重做日志来完成数据的恢复。

Checkpoint

Checkpoint(检查点)技术的目的是解决以下几个问题

  1. 缩短数据库的恢复时间;

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

  2. 缓冲池不够用时,将脏页刷新到磁盘;

  3. 重做日志不可用时,刷新脏页(redo log是重复使用的,写满了以后,需要强制触发checkpoint刷盘)

对于 InnoDB存储引擎而言,其是通过LSN( Log Sequence Number)来标记版本的。而LSN是8字节的数字,其单位是字节。每个页有LSN,重做日志中也有LSN,Checkpoint也有LSN。

checkPoint类型

在 InnoDB存储引擎内部,有两种 Checkpoint分别为:

  1. Sharp Checkpoint 所有脏页都刷盘
  2. Fuzzy Checkpoint 部分脏页刷盘

Sharp Checkpoint发生在数据库关闭时将所有的脏页都刷新回磁盘,这是默认的工作

InnoDB存储引擎中可能发生如下几种情况的Fuzzy Checkpoint

  1. Master Thread Checkpoint

    Master Thread差不多以每秒或每十秒的速度从缓冲池的脏页列表中,异步刷新一定比例的页回磁盘。

  2. FLUSH LRU LIST Checkpoint

    InnoDB存储引擎需要保证LRU列表中需要有100个空闲页可供使用,如果不足100,会将LRU列表尾部的页移除,直到100个空闲页。检查是否存在100个空闲页的操作,在Page Cleaner线程中进行。

  3. Async/Sync Flush Checkpoint

    redo log文件不可用的情况(redo log剩余可用空间小于一定阈值),这时需要强制将些页刷新回磁盘,而此时脏页是从脏页列表中选取的。(5.6版本后刷盘操作也在Page Cleaner线程中进行,不会阻塞用户线程)

  4. Dirty Page too much Checkpoint 脏页数量太多,引擎强制执行checkPoint

Master Thread 工作方式

Master Thread具有最高的线程优先级别。其内部由多个循环(loo)组成:主循环(loop)、后台循环( backgroup loop)、刷新循环( fush loop)、暂停循环( suspend loop) 。Master Thread会根据数据库运行的状态在loop、 background loop、 flush loop和 suspendl oop中进行切换。

Loop被称为主循环,因为大多数的操作是在这个循环中,其中有两大部分的操
作——每秒钟的操作和每10秒的操作。伪代码如下:

1
2
3
4
5
6
7
8
9
10
11
void master_thread(){

loop:
for(int i=0; 1<10; 1++){
do thing once per second
sleep 1 second if necessary
}
do things once per ten seconds
goto loop;
}

每秒一次的操作包括:

  1. redo log buffer刷新到磁盘(总是);
  2. 合并插入缓冲(可能);
  3. 至多刷新100个 InnoDB的缓冲池中的脏页到磁盘(引擎根据阈值判断是否需要刷页,现在已放在Page Clear线程中执行);
  4. 如果当前没有用户活动,则切换到 background loop(可能)。

接着来看每10秒的操作,包括如下内容:

  1. 刷新100个脏页到磁盘(可能的情况下);
  2. 合并至多5个插人缓冲(总是);
  3. 将日志缓冲刷新到磁盘(总是);
  4. 删除无用的Undo页(总是);
  5. 刷新100个或者10个脏页到磁盘(总是)。

删除Undo页

InnoDB存储引擎会进行一步执行full purge操作,即删除无用的Undo页。**对表进行 update、 delete这类操作时,原先的行被标记为删除,但是因为一致性
读( consistent read)的关系,需要保留这些行版本的信息。**但是在 full purge过程中,InnoDB存储引擎会判断当前事务系统中已被删除的行是否可以删除,比如有时候可能还有查询操作需要读取之前版本的undo信息,如果可以删除, InnoDB会立即将其删除。

关键特性

insert buffer(change buffer)

在MySQL5.5之前的版本中,由于只支持缓存insert操作,所以最初叫做insert buffer,只是后来的版本中支持了更多的操作类型缓存,才改叫change buffer。

对于非聚集索引的插入或更新操作,不是每一次直接插入到索引页中,而是先判断插入的非聚集索引页是否在缓冲池中,若在,则直接插入;若不在,则先放人到一个 Insert Buffer对象中,好似欺骗。

数据库这个非聚集的索引已经插到叶子节点,而实际并没有,只是存放在另一个位置。然后再以定的频率和情况进行 Insert Buffer和辅助索引页子节点的 merge(合并)操作,这时通常能将多个插入合并到一个操作中(因为在一个索引页中),这就大大提高了对于非聚集索引插入的性能。

Insert Buffer的使用需要同时满足以下两个条件:

  1. 索引是辅助索引( secondary index);

  2. 索引不是唯一( unique)的。

    辅助索引不能是唯一的,因为在插入缓冲时,数据库并不去查找索引页来判断插入的记录的唯一性。如果去査找肯定又会有离散读取的情况发生,从而导致Insert Buffer失去了意义。

两次写(double write)

double write(两次写)带给 InnoDB存储引擎的是数据页的可靠性。

double write由两部分组成,一部分是内存中的 doublewrite buffer,大小为2MB,另部分是物理磁盘上共享表空间中连续的128个页,即2个区( extent),大小同样为2MB。

第一次写

在对缓冲池的脏页进行刷新时,并不直接写磁盘,而是会通过 memcpy函数将
脏页先复制到内存中的 double write buffer,之后通过 doublewrite buffer再分两次,每次1MB顺序地写入共享表空间的物理磁盘上,然后马上调用 fsync函数,同步磁盘,避免缓冲写带来的问题。(此时缓存已经持久化到硬盘上了,避免写到一半宕机,页不完整)

在这个过程中,因为 doublewrite页是连续的,因此这个过程是顺序写的,开销并不是很大。

第二次写

在完成 doublewrite页的写入后,再将 doublewrite buffer中的页写入各个表空间文件中,此时的写人则是离散的。

自适应哈希索引

InnoDB存储引擎会监控对表上各索引页的查询。如果观察到建立哈希索引可以带来速度提升,则建立哈希索引,称之为自适应哈希索引。

文件

mysql文件,针对所有引擎

  1. 错误日志
  2. 慢查询日志
  3. 查询日志
  4. bin log

bin log

二进制日志( binary log)记录了对 MySQL数据库执行更改的所有操作,但是不包括 SELECT和SHOW这类操作。若操作本身并没有对数据发生变化,该操作也有可能被记录bin log。(update t set a=1 where a=2 ,没有a=2的记录)

二进制日志主要有以下几种作用

  1. 恢复(recovery):某些数据的恢复需要二进制日志,例如,在一个数据库全备文件恢复后,用户可以通过二进制日志进行 point-in-time的恢复。
  2. 复制(replication):其原理与恢复类似,通过复制和执行二进制日志使一台远程的 MySQL数据库(一般称为 slave)与一台 MySQL数据库(一般称为 master)进行实时同步。
  3. 审计:用户可以通过二进制日志中的信息来进行审计,判断是否有对数
    据库进行注入的攻击。

bin log 和事务

当使用事务的表存储引擎(如InnoDB存储引擎)时,所有未提交的二进制日志会被记录到一个缓存中去,等该事务提交时直接将缓冲中的二进制日志写入二进制日志文件。

在默认情况下,二进制日志并不是在每次写的时候同步到磁盘。因此,当数据库所在操作系统发生宕机时,可能会有最后一部分数据没有写入二进制日志文件中,这会给恢复和复制带来问题。

1
2
3
4
5
6
7
8
9
10
11
12

参数 sync_binlog=[N]表示每写缓冲多少次就同步到磁盘。

0 :存储引擎不进行binlog的刷新到磁盘,而由操作系统的文件系统控制缓存刷新。

1:每提交一次事务,存储引擎调用文件系统的sync操作进行一次缓存的刷新,这种方式最安全,但性能较低。

n:当提交的日志组=n时,存储引擎调用文件系统的sync操作进行一次缓存的刷新。


sync_binlog的默认值为0,不写binlog。

即使sync_binlog为1,也会有数据一致性问题,见redo log 和bin log区别

InnoDB存储引擎文件

表空间文件

  1. 默认表空间文件(共享表空间)
  2. 独立表空间文件 (通过参数开启)

每张表的独立表空间内存放的只是数据、索引和插入缓冲 Bitmap页,其他类的数据,如回滚信息,插人缓冲索引页、系统事务信息,二次写缓冲等还是存放在原来的共享表空间内。

redo log

redo log记录了InnoDB的事务日志。

每个 InnoDB存储引擎至少有1个redo log文件组( group),每个文件组下至少有个2redo log文件。默认两个,默认大小每个1G。

1
2
3
4
5
6
7
8
innodb_flush_log_at_trx_commit:

0,表示每次事务提交时都只是把redo log留在redo log buffer中;

1,表示每次事务提交时都将redo log直接持久化到磁盘;

2,表示每次事务提交时都只是把redo log写到page cache(写到磁盘(write),但是没有持久化(fsync),物理上是在文件系统的page cache里面)

redo log 和 bin log区别

  1. 作用域不同

    二进制日志会记录所有与 MYSQL数据库有关的日志记录,包括 InnodB、MyISAM、Heap等其他存储引擎的日志。而 InnoDB存储引擎的重做日志只记录有关该存储引擎本身的事务日志。

  2. 记录的内容不同

    二进制日志文是逻辑日志。而 InnoDB存储引擎的重做日志文件记录的是关于每个页(Page)的更改的物理情况。

  3. 写入的时间也不同

    二进制日志文件仅在事务提交前进行提交,即只写磁盘一次,不论这时该事务多大。而在事务进行的过程中,却不断有重做日志条目(redo entry)被写入到重做日志文件中。
    

mysql是如何解决redo log 和bin log数据不一致的?

将sync_binlog设为1,还是会有一种情况导致问题的发生。

当使用InnoDB存储引擎时,在一个事务发出COMMIT动作之前,由于sync _binlog为1,因此会将二进制日志立即写入磁盘。如果这时已经写人了二进制日志,但是提交还没有发生(没有记录到redo log),并且此时发生了宕机,那么在 MySQL数据库下次启动时,由于 COMMIT操作并没有发生,这个事务会被回滚掉。但是二进制日志已经记录了该事务信息,不能被回滚。

这个问题可以通过将参数 innodb_support_xa设为1来解决,虽然 innodb support xa与XA事务有关,但它同时也确保了二进制日志和 InnoDB存储引擎数据文件的同步。

两阶段提交