UPDATE `user_info` SET name = 'zhangsan' WHERE id = 1;
和查询语句流程一样,更新语句也会同样走一遍相同的流程,如下图:
连接器验证权限,更新数据会使缓存失效,分析器分析词法和语法,优化器选择索引,执行器调用存储引擎的接口更新数据。但是这里比较特殊的是,更新流程会涉及到两个非常重要的日志模块:redo log(重做日志)和binlog(归档日志);
一、redo log(重做日志)
MySQL中使用WAN(Write-Ahead Logging)技术,先写日志,后存磁盘,redo log就是使用的该技术;当执行一条更新语句的时候,InnoDB引擎会先把记录写到redo log中,并更新内存,这个时候更新就算完成了。同时InnoDB会在适当的时候将这个操作记录写到磁盘当中,写入磁盘的操作往往是在系统空闲的时候做,也就是先写日志,后存磁盘。
InnoDB的redo log是固定大小的,比如可以配置为一组4个文件,每个文件大小为1G,一组4个文件最多存储4G大小的日志。redolog采用循环写的方式记录,当写到结尾时,会回到开头循环写日志。当存储用完了会将日志更新到磁盘中,擦除旧的日志,从头开始写。
write pos是日志当前写的位置。check point是数据保存磁盘的位置,即当前要擦除的位置,write pos和check point之间的是redo log空闲的部分,可以接着存储日志,当write pos追上check point,表示redo日志满了,这个时候就不能再执行更新操作,需要将redo日志存入到磁盘中,然后根据check point的位置擦除redo日志文件的内容,将checkpoint往后推移,write pos之间重新出现空闲部分,继续存储redo log,知道追上check point,继续之前的循环操作。
有了redo log,InnoDB就可以保证即使数据库发生异常重启或者宕机,之前提交的记录都不会丢失,这个能力称为crash-safe。
二、binlog(归档日志)
MySQL整体来看,其实就是两部分:一部分是Server层,它主要做的是MySQL功能层面的事情;还有一部分是引擎层,负责存储相关的具体事宜。redo log是存储层InnoDB的特性日志,而binlog则是Service自己的日志。最早Mysql默认引擎为MyISAM,没有InnoDB存储引擎的,也不会记录redo log,所以也没有crash-safe的能力;而service层独立于存储层,存储的binlog日志只是用于归档。后来购买了其他公司的InnoDB以插件形式引入MySQL,这才利用InnoDB的redo log日志实现了crash-safe能力。
redo log和binglog区别:
1、redo log是InnoDB引擎特有的;binlog是MySQL的Server层实现的,所有引擎都可以使用。
2、redo log是物理日志,用于记录事务操作的变化,记录的是数据修改之后的值,不管事务是否提交都会记录下来,不记录执行的语句信息,不是二进制文件;binlog是逻辑日志, binlog是一个二进制格式的文件,用于记录用户对数据库更新的SQL语句信息,例如更改数据库表和更改内容的SQL语句都会记录到binlog里。
3、redo log是循环写的,空间固定会用完;binlog是可以追加写入的。“追加写”是指binlog文件写到一定大小后会切换到下一个,并不会覆盖以前的日志。
更新语句通过执行器和InnoDB引擎的内部流程:
1、执行器先通过InnoDB的引擎接口查询id=1这一行,ID是主键,引擎通过树搜索直接找到这一行,如果id=1的一行数据存在内存中,直接返回数据,否则先从磁盘读取到内存中,然后返回给执行器;
2、执行器拿到这行数据,修改name='zhangsan',获得新的一行数据,然后再调用InnoDB存储引擎的写接口将这行数据写入;
3、存储引擎将这行数据先存到内存中,同时将这次的更新操作记录到redo log中,此时redo log处于prepare的状态,没有commit,然后告知执行器操作完成,随时可以提交事务;
4、执行器生成这次更新操作的binlog,并把binlog写入磁盘;
5、执行器调用存储引擎的提交事务接口,引擎把刚刚写入的redo log改成commit状态,更新操作完成;
两阶段提交:
事务的两阶段提交协议保证了无论在任何情况下,事务要么同时存在于存储引擎和binlog中,要么两个里面都不存在,这就保证了主库与从库之间数据的一致性。如果数据库系统发生崩溃,当数据库系统重新启动时会进行崩溃恢复操作,存储引擎中处于prepare状态的事务会去查询该事务是否也同时存在于binlog中,如果存在就在存储引擎内部提交该事务(因为此时从库可能已经获取了对应的binlog内容),如果binlog中没有该事务,就回滚该事务。例如:当崩溃发生在写入redolog与写入binlog之间时,明显处于prepare状态的事务还没来得及写入到binlog中,所以该事务会在存储引擎内部进行回滚,这样该事务在存储引擎和binlog中都不会存在;当崩溃发生在写完binlog与commit之间时,处于prepare状态的事务存在于binlog中,那么该事务会在存储引擎内部进行提交,这样该事务就同时存在于存储引擎和binlog中。简单说,redo log和binlog都可以用于表示事务的提交状态,而两阶段提交就是让这两个状态保持逻辑上的一致。
redo log用于保证crash-safe能力。innodb_flush_log_at_trx_commit这个参数设置成1的时候,表示每次事务的redo log都直接持久化到磁盘。这个参数建议设置成1,这样可以保证MySQL异常重启之后数据不丢失。
sync_binlog这个参数设置成1的时候,表示每次事务的binlog都持久化到磁盘。这个参数也建议设置成1,这样可以保证MySQL异常重启之后binlog不丢失。