深入了解mysql中更新语句是如何工作的?

上一篇文章我详解介绍了mysql中的查询语句是如何工作的,我相信你还记得,一条查询语句需要经过 连接器、分析器、优化器、执行器,最终才能到达存储引擎。

那么,更新语句是如何工作的呢?

我们来看一下下面这条语句

update T set money = money + 1 where ID = 1;

首先可以确认的是,查询语句的那一套流程,更新语句也是同样会走一遍。

执行更新语句首先通过连接器连接数据库。

然后清空这个表的所有缓存。

接下来分析器进行词法语法校验,来识别这个语句是update语句。

然后优化器会选择ID这个索引。

最后执行器负责执行。

目前看来,更新语句和查询语句的执行好像没有什么区别。当然不是,在更新流程中,涉及到了两个重要的日志模块。这也是更新区别于查询的两个重要操作。他们是redo log(重做日志)和 binlog(归档日志)。

redo log(重做日志)InnoDB特有

mysql中每一次的更新操作都会和磁盘打交道,在磁盘中找到对应的记录进行更新,这个IO成本是很高的。为了解决这个问题,mysql使用了WAL 技术,WAL 的全称是 Write-Ahead Logging,它的关键点就是先写日志,再写磁盘。

具体来说,有一条记录需要更新,InnoDB 引擎就会先把记录先写到 redo log中,并更新内存,这个时候更新就算完成了。这里可能会有人不明白,还没有更新磁盘,为什么说更新完成了呢?大家有一点需要明白,mysql的查询是将磁盘中的信息加载到内存中,然后再返回,换言之读取的就是内存。接下来,InnoDB 引擎会在适当的时候,将这个操作记录更新到磁盘里面,而这个更新往往是在系统比较空闲的时候做。

这时候又会产生一个问题,如果说系统一直比较忙的话,难道要一直向redo log中写日志吗?这是肯定不行的,redo log是有固定大小的。比如可以配置为一组 4 个文件,每个文件的大小是 1GB,那么redo log总共就可以记录 4GB 的操作。从头开始写,写到末尾就又回到开头循环写,

有了redo log,InnoDB 就可以保证即使数据库发生异常重启,之前提交的记录都不会丢失,这个能力称为 crash-safe。

延伸一下,如果你的系统写入数据量很大的话,同时对于数据的查询不需要很及时的话,可以采用类似的思想进行操作。比如数据可以先写入到kafka中,然后由kafka写入到mysql中。

 

binlog(归档日志)

binlog(归档日志)是server层的日志,所有的引擎都可以使用。redo log 是物理日志,记录的是“在某个数据页上做了什么修改”;binlog 是逻辑日志,记录的是这个语句的原始逻辑,比如“给 ID=1 这一行的 money 字段加 1 ”。binlog 可以追加写入数据,不会覆盖之前的数据。

对这两个日志有了理解后,我们就可以回到开始时提的几个问题了。

对于更新语句的执行流程

1. 执行器通过引擎找到id=1的这行数据。如果这条记录已经在内存中了,就直接返回给执行器,否则就由磁盘读取到内存中,然后再返回。

2. 执行器拿到返回的数据,进行加1操作,得到新的一行数据,然后调用引擎的接口,将数据更新到内存中,同时写入到redo log中。此时的redo log处于prepare状态,然后告知执行器执行完成了,随时可以提交事务。

3. 执行器生成这个操作的 binlog,并把 binlog 写入磁盘。

4. 执行器调用引擎的提交事务接口,引擎把刚刚写入的 redo log 改成提交(commit)状态,更新完成。

如上图所示,我们发现redo log的写入拆成了两个步骤:prepare 和 commit,这就是"两阶段提交"。

如果你有分布式开发经验的话,应该很容易理解两阶段提交。在这里主要是为了让两份日志的逻辑保持一致,以便出现问题进行恢复的时候数据一致。因为这两份日志是不同逻辑中的,相当于分布式事务的问题。

小结

mysql中的更新语句和查询最大的区别在于很重要的两个日志,物理日志 redo log 和逻辑日志 binlog。换言之,你可以想象到,使用这两个日志是否能进行数据的恢复呢!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章