MySQL深入02-更新语句
# 更新语句执行流程
一条查询语句是经过了连接器
、分析器
、优化器
、执行器
等功能模块,最后到存储引擎执行数据的存取操作。那么对于一条更新语句到底经历了那些阶段呢?
在上一节中,我们有留意到,对于已经查询过的语句,MySQL
会从查询缓存中去找到对应的key,然后返回对应key的value,而这个过程一旦发生MySQL
的更新,这个查询缓存就会失效。更新语句就会把对应表的查询缓存失效,这是在查询缓存会执行的。
接下来,分析器会通过词法和语法解析知道这是一条更新语句。优化器决定要使用 ID 这个索引。然后,执行器负责具体执行,找到这一行,然后更新。
除此之外,还会涉及到两个重要的日志模块:
- 重做日志(redo log)
- 归档日志 (bin log)
# redo log
首先我们来了解一下WAL
(Write-Ahead Logging):
数据库中一种高效的日志 (opens new window)算法,对于非内存数据库 (opens new window)而言,磁盘I/O操作 (opens new window)是数据库效率的一大瓶颈。在相同的数据量下,采用WAL日志的数据库系统 (opens new window)在事务 (opens new window)提交时,磁盘写操作只有传统的回滚 (opens new window)日志的一半左右,大大提高了数据库磁盘I/O操作的效率,从而提高了数据库的性能。
这里我们试想MySQL
如果每一次更新操作都是去写进磁盘,那么整个IO成本和资源是非常高的,所以就会采用先写日志,再写磁盘的方式。
比如系统比较繁忙,但此时来了一条更新操作,这时候MySQL
的InnoDB
引擎就会先把记录写到redo log
,并更新内存,相当于这时候我只是拿了一张草稿纸记录了一下,但是并没有实际写入我的答题卷中。而当系统空闲的时候,InnoDB
引擎就会将这个更新操作记录更新到磁盘中,相当于我要把草稿纸的内容填写到答题卷上了。
这时候还会出现一个问题,当我们考试的时候,如果我草稿纸满了,这么办?
这时候我们就会把草稿纸已经核对的部分填写到答题卷上,然后把已经核对部分用橡皮擦掉,腾出空间方便后续的记录。
同样MySQL
也是类似的原理,只是清除部分已经录入磁盘的更新记录,为后续新的更新记录腾出空间。
write pos就是当前记录的位置,一边写一边后移,checkpoint就是当前要擦除的位置,也是往后推移并且循环的,擦除记录前要把记录更新到数据文件。
write pos 和 checkpoint 之间的是“粉板”上还空着的部分,可以用来记录新的操作。如果 write pos 追上 checkpoint,表示“粉板”满了,这时候不能再执行新的更新,得停下来先擦掉一些记录,把 checkpoint 推进一下。
有了 redo log
,InnoDB
引擎就可以保证即使数据库发生异常重启,之前提交的记录都不会丢失,这个能力称为 crash-safe
。
这就好比,你的答题卷突然摧毁了,但是你的草稿纸还保留着做题记录。
# bin log
上面所讲的redo log
是InnoDB
引擎所特有的功能,而我们知道MySQL
主要分为两大部分:Server层
和存储引擎
,而bin log
就是Server层
所特有的日志(归档日志)。
两日志的区别如下:
redo log
是InnoDB
引擎特有的;binlog
是 MySQL 的 Server 层实现的,所有引擎都可以使用。redo log
是物理日志,记录的是“在某个数据页上做了什么修改”;binlog
是逻辑日志,记录的是这个语句的原始逻辑,比如“给 ID=2 这一行的 c 字段加 1 ”。redo log
是循环写的,空间固定会用完;binlog
是可以追加写入的。“追加写”是指binlog
文件写到一定大小后会切换到下一个,并不会覆盖以前的日志。
更新流程如下:
- 执行器先找引擎取ID=2这一行。如果这行数据本来就在内存中,直接返回给执行器;否则需要从磁盘中读入内容,然后再返回。
- 执行器拿到引擎给的行数据,把这个值加上 1,比如原来是 N,现在就是 N+1,得到新的一行数据,再调用引擎接口写入这行新数据。
- 引擎将这行新数据更新到内存中,同时将这个更新操作记录到 redo log 里面,此时 redo log 处于 prepare 状态。然后告知执行器执行完成了,随时可以提交事务。
- 执行器生成这个操作的 binlog,并把 binlog 写入磁盘。
- 执行器调用引擎的提交事务接口,引擎把刚刚写入的 redo log 改成提交(commit)状态,更新完成。
# 两阶段提交
我们可以看到在更新操作的最后三步,执行了"两阶段提交",这是为了redo log和bin log两份日志之间的逻辑一致。现在我们来假设两个案例:
1、先写redo log,再写bin log
如果我们的更新操作,已经写入了redo log,但还没有写入bin log,此刻我们的数据已经发生变化了,如果此刻系统崩溃了。由于redo log相当于一个备忘录的存在,那么重启系统之后,MySQL是可以通过redo log这个备忘录去恢复数据。
但是由于没写入bin log,相当于bin log里面没有记录这条更新操作。以后如果需要使用bin log来恢复临时库,但是由于bin log没有这条记录,所以会导致我们使用bin log恢复之后,数据库这条记录还是更新操作之前的数值。
2、先写bin log,再写redo log
如果在 binlog 写完之后 系统崩溃,由于redo log里面没有记录这条操作,所以系统恢复之后,数据库这条数据的数值还是操作之前的数值。但是由于bin log记录了,之后如果我们使用bin log去恢复临时库,临时库恢复出来这条数据的数值则是操作之后的数值。
# bin log和redo log的区别
redo log
是InnoDB
引擎特有的;binlog
是 MySQL 的 Server 层实现的,所有引擎都可以使用。redo log
是物理日志,记录的是“在某个数据页上做了什么修改”;binlog
是逻辑日志,记录的是这个语句的原始逻辑,比如“给 ID=2 这一行的 c 字段加 1 ”。redo log
是循环写的,空间固定会用完;binlog
是可以追加写入的。“追加写”是指binlog
文件写到一定大小后会切换到下一个,并不会覆盖以前的日志。