mysql的文件组成以及IO操作的流程
文件组成:
主要包括数据文件、索引文件、日志文件(undolog、redolog、binlog),其中undolog和redolog是innodb存储引擎所有。
IO流程:
1.InnoDB引擎使用的是 WAL 技术(write ahead log),执行事务的时候,写完内存和日志(undolog、redolog、binlog),事务就算完成了。 异步刷内存数据到磁盘。
2.undolog、redolog、binlog比较
日志种类 | 存储内容 | 作用 | |
undolog | Innodb引擎 | 逻辑日志,记录的是数据内容。 | 提供回滚和多版本并发控制(MVCC) |
redolog | Innodb引擎 |
物理日志,记录的是数据页的物理修改。(某个磁盘位置,修改为...) |
机器崩溃恢复 |
binlog | mysql | 逻辑日志,记录的是数据内容或者SQL语句。 | 主从复制 |
3.innodb使用两阶段提交,保证数据一致性。 (保证redolog与binlog的逻辑一致性,进而保证主库与从库的数据一致性)
4.具体流程详见:
redolog-InnoDB 基本概念
redo log包括两部分:一是内存中的日志缓冲(redo log buffer),该部分日志是易失性的;二是磁盘上的重做日志文件(redo log file),该部分日志是持久的。
在概念上,innodb通过force log at commit机制实现事务的持久性,即在事务提交的时候,必须先将该事务的所有事务日志写入到磁盘上的redo log file中进行持久化。
为了确保每次日志都能写入到事务日志文件中,在每次将log buffer中的日志写入日志文件的过程中都会调用一次操作系统的fsync操作(即fsync()系统调用)。因为MySQL是工作在用户空间的,MariaDB/MySQL的log buffer处于用户空间的内存中。要写入到磁盘上的log file中(redo:ib_logfileN文件),中间还要经过操作系统内核空间的os buffer,调用fsync()的作用就是将OS buffer中的日志刷到磁盘上的log file中。
从redo log buffer写日志到磁盘的redo log file中,过程如下:
MySQL支持用户自定义在commit时如何将log buffer中的日志刷log file中。这种控制通过变量 innodb_flush_log_at_trx_commit 的值来决定。该变量有3种值:0、1、2,默认为1。但注意,这个变量只是控制commit动作是否刷新log buffer到磁盘。
-
当设置为1的时候,事务每次提交都会将log buffer中的日志写入os buffer并调用fsync()刷到log file on disk中。这种方式即使系统崩溃也不会丢失任何数据,但是因为每次提交都写入磁盘,IO的性能较差。
-
当设置为0的时候,事务提交时不会将log buffer中日志写入到os buffer,而是每秒写入os buffer并调用fsync()写入到log file on disk中。也就是说设置为0时是(大约)每秒刷新写入到磁盘中的,当系统崩溃,会丢失1秒钟的数据。
-
当设置为2的时候,每次提交都仅写入到os buffer,然后是每秒调用fsync()将os buffer中的日志写入到log file on disk。
尽管设置为0和2可以大幅度提升插入性能,但是在故障的时候可能会丢失1秒钟数据,更好的插入数据的做法是将值设置为1。
redolog-InnoDB 记录方式
-
当有一条记录需要更新的时候,InnoDB会先把记录写到redolog,并更新内存(buffer pool)
-
InnoDB会在适当的时候(例如系统空闲),将这个操作记录到磁盘里面(刷脏页)
-
-
InnoDB的redolog是固定大小的,如果每个日志文件大小为1GB,4个日志文件为一组
-
redolog的总大小为4GB,循环写
-
write pos是当前记录的位置,一边写一边后移,写到3号文件末尾后就回到0号文件开头
-
redolog是顺序写,数据文件是随机写
-
-
checkpoint是当前要擦除的位置,擦除记录前需要先把对应的数据落盘(更新内存页,等待刷脏页)
-
write pos到checkpoint之间的部分可以用来记录新的操作
-
如果write pos赶上了checkpoint,说明redolog已满,不能再执行新的更新操作,需要先推进checkpoint
-
只要write pos未赶上checkpoint,就可以执行新的更新操作
-
-
checkpoint到write pos之间的部分等待落盘(先更新内存页,然后等待刷脏页)
-
如果checkpoint赶上了write pos,说明redolog已空
-
-
-
有了redolog之后,InnoDB能保证数据库即使发生异常重启,之前提交的记录都不会丢失,达到crash-safe
-
如果redolog太小,会导致很快被写满,然后就不得不强行刷redolog,这样WAL机制的能力就无法发挥出来
binlog-Server
-
redolog是InnoDB特有的日志,binlog属于Server层日志
-
有两份日志的历史原因
-
一开始并没有InnoDB,采用的是MyISAM,但MyISAM没有crash-safe的能力,binlog日志只能用于归档
-
InnoDB是以插件的形式引入MySQL的,为了实现crash-safe,InnoDB采用了redolog的方案
-
-
binlog一开始的设计就是不支持崩溃恢复(原库)的,如果不考虑搭建从库等操作,binlog是可以关闭的(sql_log_bin)
-
redolog vs binlog
-
redolog是InnoDB特有的,binlog是MySQL的Server层实现的,所有层都可以使用
-
redolog是物理日志,记录某个数据页上做了什么修改
-
binlog是逻辑日志,记录某个语句的原始逻辑
-
逻辑日志:提供给别的引擎用,是大家都能理解的逻辑,例如搭建从库
-
物理日志:只能内部使用,其他引擎无法共享内部的物理格式
-
-
redolog是循环写,空间固定,不能持久保存,没有归档功能
-
binlog是追加写,空间不受限制,有归档功能
-
-
redolog主要用于crash-safe,原库恢复
-
binlog主要用于恢复成临时库(从库)
-
-
崩溃恢复的过程不写binlog(可能需要读binlog,如果binlog有打开,一般都会打开)
-
用binlog恢复实例(从库),需要写redolog
-
-
-
redolog对应用开发来说是透明的
-
binlog有两种模式
-
statement格式:SQL语句
-
row格式:行内容(记两条,更新前和更新后),推荐
-
日志一样的可以用于重放
-
-
update 内部流程
浅色框在InnoDB内部执行,深色框在执行器中执行(SQL: update T set c=c+1 where ID=2;)
-
执行器先通过InnoDB获取id=2这一行,id是主键,InnoDB可以通过聚簇索引找到这一行
-
如果id=2这一行所在的数据页本来就在内存(InnoDB Buffer Pool)中,直接返回给执行器
-
否则先从磁盘读入内存,然后再返回
-
-
执行器拿到InnoDB返回的行数据,进行+1操作,得到新的一行数据,再调用InnoDB的引擎接口写入这行数据
-
InnoDB首先将这行新数据更新到内存(InnoDB Buffer Pool)中,同时将这个更新操作记录到redolog(物理记录)
-
更新到内存中,在事务提交后,后续的查询就可以直接在内存中读取该数据页,但此时的数据可能还没有真正落盘
-
但在事务提交前,其他事务是无法看到这个内存修改的
-
而在事务提交后,说明已经成功写入了redolog,可崩溃恢复,不会丢数据,因此可以直接读内存的数据
-
-
刚更新的内存是不会删除的,除非内存不够用,在数据从内存删除之前,系统会保证它们已经落盘
-
此时redolog处于prepare状态(prepare标签),然后告诉执行器执行完成,随时可以提交事务
-
对其他事务来说,刚刚修改的内存是不可见的
-
-
-
执行器生成这个操作的binlog(逻辑记录)并写入磁盘
-
binlog写成功事务就算成功,可以提交事务
-
哪怕崩溃恢复,也会恢复binlog写成功的事务(此时对应的redolog处于prepare状态)
-
-
binlog如果没写成功就回滚,回滚会写redolog,打上rollback标签,binlog则会直接丢弃
-
如果binlog不丢弃,则会传播到从库
-
-
-
执行器调用InnoDB的提交事务接口,InnoDB把刚刚写入的redolog改成commit状态,更新完成
-
redolog打上了commit标签
-
commit表示两个日志都生效了
-
commit完成后才会返回客户端
-
两阶段提交
概述
-
目的:为了让redolog和binlog的逻辑一致
-
脑洞:假设不采用两阶段提交
-
有两种顺序:redolog->binlog,或者binlog->redolog
-
此时可以认为redolog和binlog完全独立
-
崩溃恢复完全依赖于redolog(原库),恢复临时库完全依赖于binlog
-
-
按照上面的两种顺序,都会导致redolog和binlog逻辑上的不一致
-
假设原库crash后执行原库恢复+执行临时库恢复,恢复出来的数据是不一致的(主从不一致)
-
-
-
恢复清醒:两阶段提交(假设是双1的配置)
-
redolog prepare + binlog成功,提交事务,崩溃恢复后也会继续提交事务(redolog commit),逻辑一致
-
redolog prepare + binlog失败,回滚事务,崩溃恢复后也会继续回滚事务(redolog rollback),逻辑一致
-
一个事务的binlog是有固定格式的
-
redolog与binlog是通过事务id(XID)进行关联的
-
此时binglog中没有对应的记录,事务记录是不完整的
-
-
崩溃恢复后是会从checkpoint开始往后主动刷数据
-
-
采用非双1的配置,在极端环境下会出现redolog与binlog不一致的情况
-
优先保证redolog,先原库崩溃恢复,再处理从库(原库可以在崩溃恢复后,重新做一次全量备份,重建从库)
-
在两阶段提交的不同瞬间,Mysql发生异常重启,如何保证数据完整性?
崩溃恢复时的判断规则:
1、如果redo log里的事务是完整的,也就是已经有了commit标识,直接提交
2、如果redo log里的事务只有完整的prepare,则判断对应的事务binlog是否存在且完整,是则提交否则回滚事务。
如果在A处出现crash:由于binlog还没写入,redo log还没提交,所以崩溃恢复时事务会回滚(删除redo log,内存回滚),可以保证数据一致性。
如果在B处出现crash:对应于上面第二种情况,如果binlog已经写完则提交,否则回滚。
F&Q
1、redo log和binlog是如何关联起来的?
它们有一个共同的数据字段:XID。崩溃恢复时会按顺序扫描redo log:
如果碰到既有prepare又有commit标识的redo log就直接提交
如果碰到只有prepare没有commit的redo log则拿着XID去binlog找对应的事务
2、处于prepare阶段的redo log+完整的binlog重启就能恢复,MYSQL为什么这样设计?
主要考虑数据一致性,在某一时刻,如果binlog写完后MYSQL发生崩溃,这时候binlog已经写入,之后会被从库(或用这个binlog恢复出的库)使用。
所以,在主库上也要提交这个事务。采用这个策略,主库和备库的数据就保证了一致性。
3、为什么是“两阶段提交”策略,先写完redo log,再写binlog,崩溃恢复的时候必须两个日志都完整才可以吗?
两阶段提交是经典的分布式系统问题,这么做主要考虑事务的持久性问题
对于InnoDB,如果redo log提交了那么事务就不能回滚了,如果redo log提交了binlog写入失败则数据又不一致了,
只要当前事务不提交它就是活跃的,对别的事务就是不可见的,一旦提交就可能落到后面启动的事务的可见区域里,提交后回滚可能覆盖掉别的事务的更新
两阶段提交是给所有人一个机会,当所有人都说“ok”的时候再一起提交。
4、不引入两个日志,也就没有两阶段提交的必要了。只用binlog支持崩溃恢复和归档可以吗?
历史层面:InnoDB不是MYSQL原生引擎,在加入MYSQL之前就已经有了redo log支持崩溃恢复和事务
实现层面:下图是只用binlog的示意图
这样的流程下,binlog 还是不能支持崩溃恢复的。
说一个不支持的点吧:binlog 没有能力恢复“数据页”。
如果在图中标的位置,也就是 binlog2 写完了,但是整个事务还没有 commit 的时候,MySQL 发生了 crash。
重启后,引擎内部事务 2 会回滚,然后应用 binlog2 可以补回来;但是对于事务 1 来说,系统已经认为提交完成了,不会再应用一次 binlog1。
但是,InnoDB 引擎使用的是 WAL 技术,执行事务的时候,写完内存和日志,事务就算完成了。如果之后崩溃,要依赖于日志来恢复数据页。
也就是说在图中这个位置发生崩溃的话,事务 1 也是可能丢失了的,而且是数据页级的丢失。此时,binlog 里面并没有记录数据页的更新细节,是补不回来的。
你如果要说,那我优化一下 binlog 的内容,让它来记录数据页的更改可以吗?但,这其实就是又做了一个 redo log 出来。
5、那可以只要redo log 不要binlog吗?
只从崩溃恢复的角度讲是可以的,关闭binlog依然是crash-safe的。
实际生产环境binlog都会是开着的,一个是归档,redo log无法记录历史日志,另外MYSQL系统依赖于binlog,比如高可用的基础就是binlog复制。
6、redo log一般设置多大?
对于常见的几个TB的硬盘,设置4个文件,每个1GB
7、正常运行的实例,数据写入后的最终落盘是从redo log更新过来的还是从buffer pool更新过来的?
redo log并没有记录数据页的完整数据,所以它没有能力自己去更新磁盘数据页
1、如果是正常运行的实例,数据页被修改后跟磁盘的数据页不一样称为脏页,最终落盘就是把内存中的数据页写入磁盘,也就是刷脏页(flush),这个过程跟redo log毫无关系
2、在崩溃恢复场景中,InnoDB如果判断到一个数据页可能在崩溃恢复时丢失了更新,就会将它读到内存然后用redo log更新内存内容,更新完成后内存就变成了上述情况的脏页。
8、redo log buffer是什么?是先修改内存还是先写redo log文件?
在一个事务的更新过程中,日志是要写多次的,如:
begin;
insert into t1 ...
insert into t2 ...
commit;
插入数据的过程中生成的日志都要先保存起来,但又不能在还没执行commit操作时直接写到redo log文件中
所以redo log buffer是一块内存,用来先存redo 日志的,也就是在执行第一个insert时数据内存被修改了,redo log buffer也写入了日志
真正把日志写到redo log文件(文件名是ib_logfile+数字)是在执行commit语句(不是commit小步骤)时做的。