Innodb引擎的4大特性
插入缓存(Insert Buffer/Change Buffer)
插入缓存之前版本叫insert buffer,现版本Change Buffer,主要提升插入性能,change buffer 是insert buffer的加强,insert buffer 只针对insert有效,change buffering 对insert、delete、update(delete + insert)、purge都有效
对于非聚聚索引来说,比如存在用户购买金额这样一个字段,索引是普通索引,每个用户的购买的金额不相同的概率比较大,这样导致可能出现购买记录的数据在数据里的排序可能是1000,3,499,35…,这种不连续的数据,一会插入这个数据页,一会插入那个数据页,这样造成的IO是很耗时的,所以出现了Insert Buffer。
Insert Buffer是怎么做的呢?mysql对于非聚集索引的插入,先去判断要插入的索引页是否已经在内存中了,如果不在,暂时不着急先把索引页加载到内存中,而是把它放到了一个Insert Buffer对象中,临时先放在这,然后等待情况,等待很多和现在情况一样的非聚集索引,再和要插入的非聚集索引页合并,比如说现在Insert Buffer中有1,99,2,100,合并之前可能要4次插入,合并之后1,2可能是一个页的,99,100可能是一个页的,这样就减少到了2次插入。这样就提升了效率和插入性能,减少了随机IO带来性能损耗。
综合上述,Insert Buffer 只对于非聚集索引(非唯一)的插入和更新有效,对于每一次的插入不是写到索引页中,而是先判断插入的非聚集索引页是否在缓冲池中,如果在则直接插入;若不在,则先放到Insert Buffer 中,再按照一定的频率进行合并操作,再写回disk。这样通常能将多个插入合并到一个操作中,目的还是减少了随机IO带来性能损耗。
使用插入缓冲的条件:
- 非聚集索引
- 非唯一索引
innodb_change_buffer设置的值有:
1 | all: 默认值,缓存insert, delete, purges操作 |
可以通过参数控制其使用的大小:
1 | mysql> show variables like 'innodb_change_buffer_max_size'; |
innodb_change_buffer_max_size,默认是25%,即缓冲池的1/4。最大可设置为50%。当MySQL实例中有大量的修改操作时,要考虑增大innodb_change_buffer_max_size
上面提过在一定频率下进行合并,那所谓的频率是什么条件?
辅助索引页被读取到缓冲池中。正常的select先检查Insert Buffer是否有该非聚集索引页存在,若有则合并插入。
辅助索引页没有可用空间。空间小于1/32页的大小,则会强制合并操作。
Master Thread 每秒和每10秒的合并操作。
双写机制(Double Write)
- doublewrite缓存位于系统表空间的存储区域,用来缓存innodb的数据页从innodb buffer pool中flush之后并写入到数据文件之前;
- 当操作系统或数据库进程在数据页写入磁盘的过程中崩溃,可以在doublewrite缓存中找到数据页的备份,用来执行crash恢复;
- 数据页写入到doublewrite缓存的动作所需要的io消耗要小于写入到数据文件的消耗,因为此写入操作会以一次大的连续块的方式写入
根据上图知道:
内存中doublewrite buffer大小2M;物理磁盘上共享表空间中连续的128个页,也就是2个区(extent)大小同样为2M;
对缓冲池脏页进行刷新时,不是直接写磁盘。
- 第一步:通过memcpy()函数将脏页先复制到内存中的doublewrite buffer;
- 第二步:通过doublewrite分两次,每次1M顺序的写入共享表空间的物理磁盘上。这个过程中,doublewrite页是连续的,因此这个过程是顺序的,所以开销并不大;
- 第三步:完成doublewrite页的写入后,再将doublewrite buffer中的页写入各个表空间文件中,此时写入是离散的,可能会较慢;
- 如果操作系统在第三步的过程中发生了崩溃,在恢复过程中,可以从共享表空间中的doublewrite中找到该页的一个副本,将其复制到表空间文件中,再应用重做日志;
自适应hash索引ahi
- innodb存储引擎会监控对表上二级索引的查找,如果发现某二级索引被频繁访问,此索引成为热数据,建立hash索引以提升查询速度,此建立是自动建立哈希索引,故称为自适应哈希索引(adaptive hash index);
- 自适应哈希索引会占用innodb buffer pool;
- 只适合搜索等值(=)的查询,对于范围查找等操作,是不能使用的;
预读
预读(read-ahead)操作是一种IO操作,用于异步将磁盘的页读取到buffer pool中,预料这些页会马上被读取到。预读请求的所有页集中在一个范围内。InnoDB使用两种预读算法:
两种预读算法来提高性能:
- 线性预读:以extent为单位,将下一个extent提前读取到buffer pool中;
- 随机预读:以extent中的page为单位,将当前extent中的剩余的page提前读取到buffer pool中;
线性预读一个重要参数:innodb_read_ahead_threshold,控制什么时间(访问extent中多少页的阈值)触发预读;
- 默认:56,范围:0~64,值越高,访问模式检查越严格;
- 没有该变量之前,当访问到extent最后一个page时,innodb会决定是否将下一个extent放入到buffer pool中;
随机预读说明:
- 当同一个extent的一些page在buffer pool中发现时,innodb会将extent中剩余page一并读取到buffer pool中;
- 随机预读给innodb code带来一些不必要的复杂性,性能上也不稳定,在5.5版本已经废弃,如果启用,需要修改变量:innodb_random_read_ahead为ON;
mysql三大日志-binlog、redo log和undo log
binlog
binlog
用于记录数据库执行的写入性操作(不包括查询)信息,以二进制的形式保存在磁盘中。 binlog
是 mysql
的逻辑日志,并且由 Server
层进行记录,使用任何存储引擎的 mysql
数据库都会记录 binlog
日志。
- 逻辑日志: 可以简单理解为记录的就是sql语句 。
- 物理日志:
mysql
数据最终是保存在数据页中的,物理日志记录的就是数据页变更
binlog
是通过追加的方式进行写入的,可以通过 max_binlog_size
参数设置每个 binlog
文件的大小,当文件大小达到给定值之后,会生成新的文件来保存日志。
binlog使用场景
在实际应用中, binlog
的主要使用场景有两个,分别是 主从复制 和 数据恢复 。
- 主从复制 :在
Master
端开启binlog
,然后将binlog
发送到各个Slave
端,Slave
端重放binlog
从而达到主从数据一致。 - 数据恢复 :通过使用
mysqlbinlog
工具来恢复数据。
binlog刷盘时机
对于 InnoDB
存储引擎而言,只有在事务提交时才会记录 biglog
,此时记录还在内存中,那么 biglog
是什么时候刷到磁盘中的呢? mysql
通过 sync_binlog
参数控制 biglog
的刷盘时机,取值范围是 0-N
:
- 0:不去强制要求,由系统自行判断何时写入磁盘;
- 1:每次
commit
的时候都要将binlog
写入磁盘; - N:每N个事务,才会将
binlog
写入磁盘。
从上面可以看出, sync_binlog
最安全的是设置是 1
,这也是 MySQL 5.7.7
之后版本的默认值。但是设置一个大一些的值可以提升数据库性能,因此实际情况下也可以将值适当调大,牺牲一定的一致性来获取更好的性能。
binlog日志格式
binlog
日志有三种格式,分别为 STATMENT
、 ROW
和 MIXED
。
在
MySQL 5.7.7
之前,默认的格式是STATEMENT
,MySQL 5.7.7
之后,默认值是ROW
。日志格式通过binlog-format
指定。
STATMENT
: 基于SQL
语句的复制(statement-based replication, SBR
),每一条会修改数据的sql语句会记录到binlog
中 。1
2* 优点: 不需要记录每一行的变化,减少了` binlog ` 日志量,节约了 ` IO ` , 从而提高了性能;
* 缺点: 在某些情况下会导致主从数据不一致,比如执行` sysdate() ` 、 ` slepp() ` 等 。ROW
: 基于行的复制(row-based replication, RBR
),不记录每条sql语句的上下文信息,仅需记录哪条数据被修改了 。- 优点: 不会出现某些特定情况下的存储过程、或function、或trigger的调用和触发无法被正确复制的问题 ;
- 缺点: 会产生大量的日志,尤其是
alter table
的时候会让日志暴涨
MIXED
: 基于STATMENT
和ROW
两种模式的混合复制(mixed-based replication, MBR
),一般的复制使用STATEMENT
模式保存binlog
,对于STATEMENT
模式无法复制的操作使用ROW
模式保存binlog
redo log
为什么需要redo log
我们都知道,事务的四大特性里面有一个是 持久性 ,具体来说就是
只要事务提交成功,那么对数据库做的修改就被永久保存下来了,不可能因为任何原因再回到原来的状态 。那么 mysql
是如何保证一致性的呢?最简单的做法是在每次事务提交的时候,将该事务涉及修改的数据页全部刷新到磁盘中。但是这么做会有严重的性能问题,主要体现在两个方面:
- 因为
Innodb
是以页
为单位进行磁盘交互的,而一个事务很可能只修改一个数据页里面的几个字节,这个时候将完整的数据页刷到磁盘的话,太浪费资源了! - 一个事务可能涉及修改多个数据页,并且这些数据页在物理上并不连续,使用随机IO写入性能太差!
因此 mysql
设计了 redo log
, 具体来说就是只记录事务对数据页做了哪些修改
,这样就能完美地解决性能问题了(相对而言文件更小并且是顺序IO)
redo log基本概念
redo log
包括两部分:一个是内存中的日志缓冲( redo log buffer
),另一个是磁盘上的日志文件( redo log file
)。 mysql
每执行一条 DML
语句,先将记录写入 redo log buffer
,后续某个时间点再一次性将多个操作记录写到 redo log file
。这种 先写日志,再写磁盘 的技术就是 MySQL
里经常说到的 WAL(Write-Ahead Logging)
技术。
在计算机操作系统中,用户空间( user space
)下的缓冲区数据一般情况下是无法直接写入磁盘的,中间必须经过操作系统内核空间( kernel space
)缓冲区( OS Buffer
)。因此, redo log buffer
写入 redo log file
实际上是先写入 OS Buffer
,然后再通过系统调用 fsync()
将其刷到 redo log file
中,过程如下:
mysql
支持三种将 redo log buffer
写入 redo log file
的时机,可以通过 innodb_flush_log_at_trx_commit
参数配置,各参数值含义如下:
参数值 | 含义 |
---|---|
0(延迟写) | 事务提交时不会将 redo log buffer 中日志写入到 os buffer ,而是每秒写入 os buffer 并调用 fsync() 写入到 redo log file 中。也就是说设置为0时是(大约)每秒刷新写入到磁盘中的,当系统崩溃,会丢失1秒钟的数据。 |
1(实时写,实时刷) | 事务每次提交都会将 redo log buffer 中的日志写入 os buffer 并调用 fsync() 刷到 redo log file 中。这种方式即使系统崩溃也不会丢失任何数据,但是因为每次提交都写入磁盘,IO的性能较差。 |
2(实时写,延迟刷) | 每次提交都仅写入到 os buffer ,然后是每秒调用 fsync() 将 os buffer 中的日志写入到 redo log file 。 |
redo log记录形式
前面说过, redo log
实际上记录数据页的变更,而这种变更记录是没必要全部保存,因此 redo log
实现上采用了大小固定,循环写入的方式,当写到结尾时,会回到开头循环写日志。如下图:
同时我们很容易得知, 在innodb中,既有redo log
需要刷盘,还有 数据页
也需要刷盘, redo log
存在的意义主要就是降低对 数据页
刷盘的要求 。在上图中, write pos
表示 redo log
当前记录的 LSN
(逻辑序列号)位置, check point
表示 数据页更改记录** 刷盘后对应 redo log
所处的 LSN
(逻辑序列号)位置。 write pos
到 check point
之间的部分是 redo log
空着的部分,用于记录新的记录;check point
到 write pos
之间是 redo log
待落盘的数据页更改记录。当 write pos
追上 check point
时,会先推动 check point
向前移动,空出位置再记录新的日志。
启动 innodb
的时候,不管上次是正常关闭还是异常关闭,总是会进行恢复操作。因为 redo log
记录的是数据页的物理变化,因此恢复的时候速度比逻辑日志(如 binlog
)要快很多。 重启 innodb
时,首先会检查磁盘中数据页的 LSN
,如果数据页的 LSN
小于日志中的 LSN
,则会从 checkpoint
开始恢复。 还有一种情况,在宕机前正处于checkpoint
的刷盘过程,且数据页的刷盘进度超过了日志页的刷盘进度,此时会出现数据页中记录的 LSN
大于日志中的 LSN
,这时超出日志进度的部分将不会重做,因为这本身就表示已经做过的事情,无需再重做。
redo log与binlog区别
redo log | binlog | |
---|---|---|
文件大小 | redo log 的大小是固定的。 |
binlog 可通过配置参数 max_binlog_size 设置每个binlog 文件的大小。 |
实现方式 | redo log 是 InnoDB 引擎层实现的,并不是所有引擎都有。 |
binlog 是 Server 层实现的,所有引擎都可以使用 binlog 日志 |
记录方式 | redo log 采用循环写的方式记录,当写到结尾时,会回到开头循环写日志。 | binlog通过追加的方式记录,当文件大小大于给定值后,后续的日志会记录到新的文件上 |
适用场景 | redo log 适用于崩溃恢复(crash-safe) |
binlog 适用于主从复制和数据恢复 |
由 binlog
和 redo log
的区别可知: binlog
日志只用于归档,只依靠 binlog
是没有 crash-safe
能力的。但只有 redo log
也不行,因为 redo log
是 InnoDB
特有的,且日志上的记录落盘后会被覆盖掉。因此需要 binlog
和 redo log
二者同时记录,才能保证当数据库发生宕机重启时,数据不会丢失。
redo log是什么,为什么需要redo log
- redo log 是重做日志。
- 它记录了数据页上的改动。
- 它指事务中修改了的数据,将会备份存储。
- 发生数据库服务器宕机、或者脏页未写入磁盘,可以通过redo log恢复。
- 它是Innodb存储引擎独有的
为什么需要 redo log?
- redo log主要用于MySQL异常重启后的一种数据恢复手段,确保了数据的一致性。
- 其实是为了配合MySQL的WAL机制。因为MySQL进行更新操作,为了能够快速响应,所以采用了异步写回磁盘的技术,写入内存后就
- 返回。但是这样,会存在crash后内存数据丢失的隐患,而redo log具备crash safe的能力。
什么是WAL技术, 好处是什么.
- WAL,中文全称是Write-Ahead Logging,它的关键点就是日志先写内存,再写磁盘。MySQL执行更新操作后,在真正把数据写入到磁盘前,先记录日志。
- 好处是不用每一次操作都实时把数据写盘,就算crash后也可以通过redo log恢复,所以能够实现快速响应SQL语句。
redo log的写入方式
redo log包括两部分内容,分别是内存中的日志缓冲(redo log buffer)和磁盘上的日志文件(redo log file)。
mysql每执行一条DML语句,会先把记录写入redo log buffer,后续某个时间点再一次性将多个操作记录写到redo log file。这种先写日志,再写磁盘的技术,就是WAL。
在计算机操作系统中,用户空间(user space)下的缓冲区数据,一般是无法直接写入磁盘的,必须经过操作系统内核空间缓冲区(即OS Buffer)。
- 日志最开始会写入位于存储引擎Innodb的redo log buffer,这个是在用户空间完成的。
- 然后再将日志保存到操作系统内核空间的缓冲区(OS buffer)中。
- 最后,通过系统调用
fsync()
,从OS buffer写入到磁盘上的redo log file中,完成写入操作。这个写入磁盘的操作,就叫做刷盘。
我们可以发现,redo log buffer写入到redo log file,是经过OS buffer中转的。其实可以通过参数innodb_flush_log_at_trx_commit
进行配置,参数值含义如下:
- 0:称为延迟写,事务提交时不会将redo log buffer中日志写入到OS buffer,而是每秒写入OS buffer并调用写入到redo log file中。
- 1:称为实时写,实时刷”,事务每次提交都会将redo log buffer中的日志写入OS buffer并保存到redo log file中。
- 2:称为实时写,延迟刷。每次事务提交写入到OS buffer,然后是每秒将日志写入到redo log file。
Redo log的执行流程
我们来看下Redo log的执行流程,假设执行的SQL如下:
1 | update T set a =1 where id =666 |
redo log的执行流程
- MySQL客户端将请求语句
update T set a =1 where id =666
,发往MySQL Server层。 - MySQL Server 层接收到SQL请求后,对其进行分析、优化、执行等处理工作,将生成的SQL执行计划发到InnoDb存储引擎层执行。
- InnoDb存储引擎层将a修改为1的这个操作记录到内存中。
- 记录到内存以后会修改redo log 的记录,会在添加一行记录,其内容是需要在哪个数据页上做什么修改。
- 此后,将事务的状态设置为prepare ,说明已经准备好提交事务了。
- 等到MySQL Server层处理完事务以后,会将事务的状态设置为commit,也就是提交该事务。
- 在收到事务提交的请求以后,redo log会把刚才写入内存中的操作记录写入到磁盘中,从而完成整个日志的记录过程。
默认状态时先写入 磁盘redolog 再进行事务commit
redo log 为什么可以保证crash safe机制呢?
- 因为redo log每次更新操作完成后,就一定会写入的,如果写入失败,说明此次操作失败,事务也不可能提交。
- redo log内部结构是基于页的,记录了这个页的字段值变化,只要crash后读取redo log进行重放,就可以恢复数据。
binlog的概念是什么, 起到什么作用, 可以保证crash-safe吗?
- bin log是归档日志,属于MySQL Server层的日志。可以实现主从复制和数据恢复两个作用。
- 当需要恢复数据时,可以取出某个时间范围内的bin log进行重放恢复。
- 但是bin log不可以做crash safe,因为crash之前,bin log可能没有写入完全MySQL就挂了。所以需要配合redo log才可以进行crash safe。
binlog和redolog的不同点有哪些?
执行器和innoDB在执行update语句时候的流程是什么样的?
- 执行器在优化器选择了索引后,会调用InnoDB读接口,读取要更新的行到内存中
- 执行SQL操作后,更新到内存,然后写redo log,写bin log,此时即为完成。
- 后续InnoDB会在合适的时候把此次操作的结果写回到磁盘。
如果数据库误操作, 如何执行数据恢复?
数据库在某个时候误操作,就可以找到距离误操作最近的时间节点的bin log,重放到临时数据库里,然后选择误删的数据节点,恢复到线上数据库。
什么是MySQL两阶段提交, 为什么需要两阶段提交?
其实所谓的两阶段就是把一个事务分成两个阶段来提交。
两阶段提交
两阶段提交主要有三步曲:
- redo log在写入后,进入prepare状态
- 执行器写入bin log
- 进入commit状态,事务可以提交。
为什么需要两阶段提交呢?
- 如果不用两阶段提交的话,可能会出现这样情况:bin log写入之前,机器crash导致需要重启。重启后redo log继续重放crash之前的操作,而当bin log后续需要作为备份恢复时,会出现数据不一致的情况。
- 如果是bin log commit之前crash,那么重启后,发现redo log是prepare状态且bin log完整(bin log写入成功后,redo log会有bin log的标记),就会自动commit,让存储引擎提交事务。
- 两阶段提交就是为了保证redo log和binlog数据的安全一致性。只有在这两个日志文件逻辑上高度一致了。你才能放心的使用redo log帮你将数据库中的状态恢复成crash之前的状态,使用binlog实现数据备份、恢复、以及主从复制。
如果不是两阶段提交, 先写redo log和先写bin log两种情况各会遇到什么问题?
- 先写redo log,crash后bin log备份恢复时少了一次更新,与当前数据不一致。
- 先写bin log,crash后,由于redo log没写入,事务无效,所以后续bin log备份恢复时,数据不一致。
binlog刷盘机制
所有未提交的事务产生的binlog,都会被先记录到binlog的缓存中。等该事务提交时,再将缓存中的数据写入binlog日志文件中。缓存的大小由参数binlog_chache_size
控制。
binlog什么时候刷新到磁盘呢?由参数sync_binlog
控制
- 当
sync_binlog
为0时,表示MySQL不控制binlog的刷新,而是由系统自行判断何时写入磁盘。选这种策略,一旦操作系统宕机,缓存中的binlog就会丢失。 sync_binlog
为N时,每N个事务,才会将binlog写入磁盘。。- 当
sync_binlog
为1时,则表示每次commit,都将binlog 写入磁盘。
来看一个比较完整的流程图吧:
说说Redo log的记录方式
redo log的大小是固定。它采用循环写的方式记录,当写到结尾时,会回到开头循环写日志。如下图(图片来源网络):
redo log 循环写入
redo log buffer(内存中)是由首尾相连的四个文件组成的,它们分别是:ib_logfile_1、ib_logfile_2、ib_logfile_3、ib_logfile_4。
- write pos表示当前写入记录位置(写入磁盘的数据页的逻辑序列位置)
- check point表示刷盘(写入磁盘)后对应的位置。
- write pos到check point之间的部分用来记录新日志,也就是留给新记录的空间。
- check point到write pos之间是待刷盘的记录,如果不刷盘会被新记录覆盖。
有了 redo log,当数据库发生宕机重启后,可通过 redo log将未落盘的数据(check point之后的数据)恢复,保证已经提交的事务记录不会丢失,这种能力称为crash-safe。