前面的专栏,我们知道了一个select语句的查询过程,我们知道当连接器连接完成之前先会查询缓存,如果有就会快很多,但是频繁的update操作又会让查询缓存很鸡肋,如果查询不到就会进入分析环节和优化环节,最后是执行环节,这样一个sql语句就执行完了,那么这次我们就来说一下,更新语句的执行过程。
mysql可以恢复到半个月内任何一秒的状态,那么这是怎么做到的呢?
首先还是从,一个普通的update语句去说:
创建一张表,插入原始数据
然后更新数据,使用update:
其实更新的流程,也会按照查询的流程走一遍:
执行命令之前,肯定要先连接数据库,这是连接器的工作。
之前我们说过,update表的时候,查询缓存会失效,然后分析器会通过词法分析,找到update关键字,知道这是一个更新语句。优化器会选择ID作为索引,然后执行器进行具体执行,找到这一行进行更新。
和查询流程不一样的是,update还涉及到了两个重要的日志模块:redo log(重做日志),binlog(归档日志)。
重要的日志模块 redo Log
用白话来说 ,当我们的数据量相当大的时候,找到要修改的某一行或者某几行,是很浪费性能的,所以mysql就通过此日志模块,将要修改的东西进行临时记录,当适当的时候再进行更新。
用文学点的话来说,mysql的每一次更新操作都会写进磁盘,然后找到记录,更新。整个过程的IO成本和查询成本都很高,所以就借助了mysql的WAL技术,WAL的全称是WriteAhead Logging,它的关键点在于先写日志,再写磁盘。具体来说,当检索一条记录需要更新的时候,就会用InnoDB引擎先把记录写到redo log,然后更新内存,然后再适当的时候,更新硬盘。
redo Log的大小是固定的,当写满后,会从开头覆盖读写。有了redo Log,即使数据库崩溃,也可以将提交的记录保存,这种能力称之为crash-safe。
重要的日志模块 bin Log
刚才我们讲了很多关于日志模块,redo Log的事情,我们知道,mysql的整体,分为两大部分,一部分是Server层,一部分是InnoDB层,刚才所讲的就是InnoDB层引擎独有的日志,而Server层也有自己的日志,称为binLog(归档日志)
那么,mysql拥有这两种日志的意义何在呢?
因为在早期的mysql中并没有InnoDB引擎,Mysql自带的引擎是MylSAM,但是MylSAM没有crash-safe的能力,binLog日志只能用于归档。而InnoDB是另一个公司以插件形式引入Mysql的,因为binLog没有crash-safe功能,所以引入redo Log就正好实现了。
这两种日志有以下三种的不同:
- redo Log是InnoDB引擎特有的;bin Log是Mysql的Server层实现的,所有的引擎都可以使用。
- redo Log是物理日志,记录的是在某个数据页上做了什么修改;bin Log是逻辑日志,记录的是语句的原始逻辑,也就是所谓的白话逻辑。
- redo Log写完第一轮后会从头覆盖着写第二轮,空间大小固定;bin Log在固定单体文件大小的情况下,会追加去写。
理解了以上两种日志之后,接下来我们来看看,执行器和InnoDB在update语句内部的流程:
- 执行器先找引擎取ID=2这一行。ID是主键,引擎直接用树搜索找到这一行。如果ID=2这一行所在的数据页本来就在内存中,就直接返回给执行器;否则,需要先从磁盘读入内存,然后再返回。
- 执行器拿到引擎给的行数据,把这个值加上1,比如原来是N,现在就是N+1,得到新的一行 数据,再调用引擎接口写入这行新数据。
- 引擎将这行新数据更新到内存中,同时将这个更新操作记录到redo log里面,此时redo log处 于prepare状态。然后告知执行器执行完成了,随时可以提交事务。
- 执行器生成这个操作的binlog,并把binlog写入磁盘。
- 执行器调用引擎的提交事务接口,引擎把刚刚写入的redo log改成提交(commit)状态,更新完成。
以下是流程图:
最后一个阶段,写入 redo Log和写入bin Log,被称为两阶段提交。
两阶段提交
目的是为了让两份日志之间逻辑一致。这时候就可以回答之前的问题了。
为什么可以让数据库恢复半个月之前任何一秒的状态
binLog记录所有的逻辑操作。换句话说,如果DBA在可承受范围内,那么就可以找到这个范围所有时间内的bin Log。
如果真的发生了误删的操作,就这样:
- 首先,找到最近的一次全量备份,如果你运气好,可能就是昨天晚上的一个备份,从这个备份恢复到临时库。
- 然后,从备份的时间点开始,将备份的binlog依次取出来,重放到中午误删表之前的那个时刻。
这样就可以将表数据从临时库提出恢复到线上。
那么,为什么需要两阶段提交呢
提交顺序很重要吗?我们可以假设这样的情况:
仍然用前面简单的update举例子:
- 假设我们初始值定的c = 5 ID = 2,这时候写入了日志,但是更新之后,未写入日志之前,发生了crash,会怎么样呢?
- 先写redoLog 在写binLog。
假设,binLog还没写完就crash了,mysql异常重启,但是我们已经写完了redoLog,只是还没写入磁盘,正在内存里了,所以不影响。但是binLog确实收到了损害,影响就是,我们没有备份这个逻辑语句,所以如果再回头找这条update语句,那就找不到了,那就肯定不行了呢。
- 先写binLog,再写redoLog
假如写完binLog,然后写redoLog,那么就会update失败,但是逻辑存入了临时的模块日志,也就是binLog,当我们重启服务之后,用binLog恢复,就会多出一个事务,所以就会和原来的表中值不一样。所以其实这样是不好的。
但我们不光在这种情况需要binLog进行恢复,如果我们要搭建一些备库来增加系统的读能力,也是需要binLog来实现,那么就会出现第二种情况,主从数据库不一致。