mysql是目前市面上应用非常广泛的关系型数据库.
当插入,更新,删除等sql语句运行后,mysql为何总能高效,快速的执行,而且不管是断电,mysql进程崩溃,还是一些其它非正常因素,mysql总能保持数据完整,
本文将带着这些问题探秘mysql底层默认存储引擎InnoDB(Mysql5.5之后)的执行过程.
当提交一个事务时,实际上它干了如下2件事:
一: InnoDB存储引擎把事务写入日志缓冲(log buffer),日志缓冲把事务刷新到事务日志.
二: InnoDB存储引擎把事务写入缓冲池(Buffer pool).
做完上面2件事,整个事务提交就完成了.
InnoDB通过事务日志把随机IO变成顺序IO,这大大的提高了InnoDB写入时的性能.
因为把缓冲池的脏页数据刷新到磁盘可能会涉及大量随机IO,这些随机IO会非常慢,通过事务日志,避开随机IO,用顺序IO替代它.
但如果此时机器断电或者意外崩溃,那脏页数据没刷新到磁盘,岂不是数据会丢失?
答案是否定的, mysql意外崩溃后,重启时.会根据事务日志重做事务,恢复所有buffer pool中丢失的脏页.
上面的过程是在未开启binlog的情况下的执行过程,binlog的基本配置如下:
#【开启二进制日志】
log_bin = mysql-bin
server_id = 2
#【中继日志的位置和命名】
relay_log = mysql-relay-bin
# 【允许备库将其重放的事件也记录到自身的二进制文件中】
log_slave_updates = 1
#【sync_binlog 实时刷新】
sync_binlog = 1
binlog的相关情况不再本文的介绍范围内,不再展开说明.
让我们来看看InnoDB的缓存和文件关系,如图1:
图1
这里面有几个核心的组件:
事务提交后,InnoDB首先在缓冲池中找到对应的页,把事务更新到缓冲区中.
当刷新脏页到磁盘时,缓冲区都干了什么?
缓冲区把脏页拷贝到double write buffer,double wirte buffer把脏页刷新到double write磁盘(这也是一次顺序IO),再把脏页刷新到数据文件中.
当然缓冲池中还有其他组件,也非常重要,如插入缓冲,该缓冲区是为了高效维护二级非唯一索引所做的优化,把多次IO转化为一次IO来达到快速更新的目的.这里不再展开说明.
InnoDB使用日志来减少事务提交时的开销.因为日志记录了事务,就无须在每个事务提交时把缓冲池中的脏块刷新到磁盘.因为刷新缓冲池到磁盘一般是随机IO.
InnoDB的日志缓冲有两个重要的参数需要介绍下:
innodb_log_buffer_size 日志缓冲区大小(5.6 默认8M,一般不需要设置太大,除非有BLOB字段)
innodb_flush_log_at_trx_commit 这是InnoDB刷新事务日志的策略参数,默认为1.
刷新策略值:
0, 一秒钟刷新一次,事务提交时,不做任何操作.(可能丢失1秒钟事务数据)
1, 每次事务都提交刷新到持久化存储(默认&最安全)
2, 每次提交时把日志缓冲写到日志文件,但并不刷新.
1和3的区别是: mysql进程挂了,3不会丢事务. 服务器断电或者挂了, 都丢失事务. 把缓冲写到日志是简单的把数据从INNODB的内存缓冲转移到操作系统缓冲.
这里面有2个重要的配置参数需要说明下.
2.1) innodb_log_file_size mysql 5.6默认的大小是50M
2.2) innodb_log_files_in_group mysql5.6默认是2,如下图:
也就是说,InnoDB默认的事务日志文件大小总和是100M。这对高性能工作来说可能太小了,有时需要几百兆甚至几个G的事务日志空间.
linux可通过/etc/my.cnf 来修改事务日志文件的大小, windows是my.ini配置文件
innodb日志是环行方式写的:当写到日志的尾部,会重新跳转到开头继续写,但不会覆盖还没应用到数据文件的日志记录,因为这样会清理掉已提交事务的唯一持久化记录.
日志文件太小,InnoDB将必须做更多的检查点,导致更多的日志写,在日志没有空间继续写入前,必须等待变更被应用到数据文件,写语句可能会被拖累.
但日志文件太大,在崩溃恢复时InnoDB可能不得不做大量的工作,增加恢复时间. 应该在这之间找到平衡,设置合适的日志大小.
缓冲池刷新脏页面到磁盘时,首先把它们写到双写缓冲,然后再把它们写到所属的数据区域中.
那岂不是所有的脏页都需要写2遍?对,就是写2遍. 但双写缓冲是顺序的,对写冲击比较小.
有些备库上可以禁止双写缓冲,另外一些文件系统(ZFS)做了同样的事,所以没必要让InnoDB做2次, innodb_doublewirte 来关闭。
InnoDb用双写缓冲来避免页没写完整所导致的数据损坏.
双写缓冲的架构如下图:
从缓冲池中拷贝页到double_write_buffer,double_write_buffer刷新到double_write(共享表空间),再调fsync()同步磁盘
总结:
1, InnoDB提交事务过程如下:
1.1): 把事务写入日志缓冲(log buffer),日志缓冲把事务刷新到事务日志.
1.2): 把事务写入缓冲池(Buffer pool).
2, Innodb存储引擎在事务提交后,是把随机IO转化为顺序IO来达到快速提交事务的目的.
3, 每次刷新脏页到磁盘,实际上是2次写页到磁盘.
3.1): 刷新脏页到双写缓冲,顺序IO
3.2): 调用一次fsync()刷新到磁盘,随机IO
4, 宕机或者意外崩溃重启mysql时,根据事务日志来重做日志恢复缓冲池未来得及刷新到磁盘的脏页,保证数据完整性.
最后给大家提一个问题:
Q: 如果发生写失效(页16KB数据,只写了8Kb),可以通过重做日志进行恢复,为什么还需要double_write?