据说MySQL能恢复到半个月内任意一秒的状态mysql
要从一条更新语句提及,若是将ID=2这一行的值+1,SQL语句能够这样写:sql
mysql> update T set c=c+1 where ID=2;
复制代码
执行一条更新语句一样会走一遍查询语句的流程:数据库
- 链接数据库
- 清空该表涉及到的缓存
- 分析器经过词法和语法解析出这是一条更新语句,并肯定涉及到表与字段
- 优化器决定使用
"ID"
这个索引
- 执行器找到这一行数据,而后进行更新操做
与查询过程不一样的是,更新涉及到两个日志模块:redo log(重作日志)和bin log(归档日志)
临时记录:redo log
酒店掌柜有一个帐本和一个小黑板,来作赊帐的记录。有如下两种方案:缓存
- 每一笔帐都打开帐本作记录,当有人还帐时,找到对应的赊帐记录,修改记录的状态
- 先在黑板上记录本次要作的操做,打烊后按照黑板上的记录向帐本上进行核算
当生意红火,顾客络绎不绝时,第一种方案效率实在是低下,掌柜的必定按照第二种方案来记帐。
一样的,MySQL若是每次更新操做都要写入磁盘,在磁盘中找到对应记录,而后更新,这个过程的IO成本、查找成本都过高了。
为了解决和这个问题,MySQL就使用了相似于黑板-帐本
模式来提升效率。这一模式即为WAL技术
,全程为Write-Ahead Logging
,关键点:先写日志,再写磁盘,也就是前文中先写黑板,再写帐本。并发
具体步骤以下:
当有记录须要更新,innoDB先把记录写入redo log中,并更新内存,这是更新操做就算结束了。innoDB引擎会在适当的时候讲操做记录更新到磁盘里,这一动做通常是系统比较闲的时候作的。
redo log的大小是固定的,共有4个文件组成,每一个大小为1G。逻辑上能够将4个文件理解为环形,从头开始写,写到末尾又从新开始新的一轮,以下图所示 post
write pos为当前记录位置,check point为当前擦除点的位置,当记录更新时,check point会随着文件的记录向后移动。擦除后未写入的位置能够记录新的操做。当write pos追上了check point,则须要停下来写入动做,将redo log内容写入磁盘,而后清除check point向后移动。
有了redo log,innoDB能够知晓每一次操做,保证当数据库发生异常重启时,以前的可以根据redo log恢复以前的记录,这种能力叫作
"crash-safe"
。
归档日志:bin log
redo log与bin log日志的区别:学习
- redo log 是属于innoDB引擎全部,bin log是server提供的,全部引擎均可以使用
- redo log 属于物理日志,记录
"在某个数据页作了什么修改"
,bin log记录的是该语句逻辑日志"将ID=2的这一行c的值+1"
- redo log日志文件是循环使用的,空间有使用完的时刻,bin log是追加记录的,不会覆盖以前的记录
也就是说,server搭配其余引擎是没有redo log的,所以也就没有了crash-safe能力优化
更新具体流程
基于对两个日志文件的了解,再次深刻了解更新的流程spa
- 执行器先经过引擎使用树搜索找到ID=2这一行,若是该记录所在的数据页自己就在内存中,则直接返回执行器,不然先从磁盘读入内存,而后返回
- 执行器拿到数据后将c的值加一,而后经过引擎的写入接口将修改后的数据写入
- 引擎j将新数据更新到内存中,而后在redo log中记录这次修改,这时redo log中该记录的状态置为prepare,并告知执行器已经更新完成,随时能够提交事务
- 执行器生成这次操做的bin log,将bin log写入磁盘中
- 执行器调用引擎的提交事务接口,引擎将刚刚写入的redo log置为commit状态,更新结束
下图是《MySQL实战》提供的流程图: 线程
浅色表明在innoDB中执行,深色在server中执行
两阶段提交
从上图能够看出,redo log是分两个阶段来提交的,这是为了保持两个日志逻辑上一致 若是不用两阶段提交会发生什么呢 利用反证法来看下:
假设初始ID=2的数据行,c的值为0,如今要执行c+1的操做。
- 先记录redo log 后记录bin log 若是刚记录完redo log,尚未记录bin log时,c的值已经记录变为1,这时MySQL服务崩溃重启,根据crash-safe机制,能够用redo log来恢复数据库,恢复后的数据中c的值为1。因为bin log中没有记录这一变化,之后备份bin log时,c的值仍是0。若是有一天须要从bin log回复一台备用数据库,因为bin log少了一次更新,则最后恢复出来的c值仍然为0,与原库中值不符合
- 先记录bin log 后记录redo log 写完bin log就发生crash,还没来得及写入redo log,崩溃恢复后这个事务是无效的,所以c的值仍是0,可是bin log中已经记录了"将c的值+1"的日志,因此用bin log恢复出来的数据多出来一个事务,使得c的值为1,与原库中数据不符。
- 两阶段提交 记录过bin log回过头提交commit(可参见评论区知识点) 更新redo log后,尚未记录bin log时崩溃,这时redo log的状态仍是prepare,事务并无提交,并且bin log中没有记录,所以因为crash-safe机制,并不会恢复该记录,c的值仍然为0,因为bin log中没有记录,之后从bin log恢复数据时,c的值在此操做中并无记录变化,所以仍是0,与原库中数据一致;另外一种状况:更新redo log,也更新了bin log,下一步执行器调用commit接口前崩溃,这时虽然redo log中状态为prepare,可是从bin log中查到有记录,因此仍是会从redo log中恢复c=1,后面直接从bin log恢复出新的数据库时,由于已经记录c的值+1,因此与原库中的值相同
总结以下:
两种方式肯定记录完整:
- redo log状态为 commit
- redo log状态为prepare而且bin log记录完整 (提交commit以前)
总结
这节主要学习了两个日志文件的用法 redo log 用于保证 crash-safe 能力,bin log用于恢复数据的完整性
- innodb_flush_log_at_trx_commit 这个参数设置成 1 的时候,表示每次事务的 redo log 都直接持久化到磁盘。这样能够保证 MySQL 异常重启以后数据不丢失。
- sync_binlog 这个参数设置成 1 的时候,表示每次事务的 binlog 都持久化到磁盘。这样能够保证 MySQL 异常重启以后 binlog 不丢失。
评论区知识点:
-
binlog没有被用来作崩溃恢复,binlog是能够关的,你若是有权限,能够"set sql_log_bin=0"
关掉本线程的binlog日志。 因此只依赖binlog来恢复就靠不住的
-
@高枕 同窗的评论简单精炼的表达了两阶段提交机制下的工做状态:
记录日志共有三个过程:
- prepare阶段
- 写binlog
- commit
- 当在2以前崩溃时
重启恢复:后发现没有commit,回滚。备份恢复:没有binlog。备份与原库一致
- 当在3以前崩溃时
重启恢复:虽没有commit,但知足prepare和binlog完整,因此重启后会自动commit。备份:有binlog. 备份与原库一致
-
来自@黄金的太阳
- redo log自己也是文件,记录文件的过程其实也是写磁盘,那和文中提到的离线写磁盘操做有何区别?
- 响应一次SQL我理解是要同时操做两个日志文件?也就是写磁盘两次?
-
写redo log是顺序写,不用去“找位置”,而更新数据须要找位置,所以redo log写的速度更快
-
实际上是3次(redolog两次 binlog 1次)。不过在并发更新的时候会合并写
本文中含有极客时间《MySQL实战》的图和部分原文,若有侵权,请联系我会马上删除
第一节:ACID之I:事务隔离