一条SQL更新语句的执行过程

前言

在一个表上有更新的时候,跟这个表有关的查询缓存会失效,所以这条语句就会把表 T 上所有缓存结果都清空。这也就是一般不建议使用查询缓存的原因。更新流程还涉及两个重要的日志模块,redo log(重做日志)和 binlog(归档日志)。

redolog(重做日志)

redo log 是 InnoDB 独有的,是在存储引擎层的。

如果每一次的更新操作都需要写进磁盘,然后磁盘也要找到对应的那条记录,然后再更新,整个过程 IO 成本、查找成本都很高。为了解决这个问题。MySql 提供 WAL 技术, WAL 的全称是 Write-Ahead Logging,它的关键点就是先写日志,再写磁盘。

当有一条记录需要更新的时候,InnoDB 引擎就会先把记录写到 redo log 里面,并更新内存,这个时候更新就算完成了。同时,InnoDB 引擎会在适当的时候,将这个操作记录更新到磁盘里面,而这个更新往往是在系统比较空闲的时候做。

InnoDB 的 redo log 是固定大小的。

write pos 是当前记录的位置,一边写一边后移,写到第 3 号文件末尾后就回到 0 号文件开头。checkpoint 是当前要擦除的位置,也是往后推移并且循环的,擦除记录前要把记录更新到数据文件。write pos 追上 checkpoint,这时候不能再执行新的更新,得停下来先擦掉一些记录,把 checkpoint 推进一下。

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

redo log 用于保证 crash-safe 能力。innodb_flush_log_at_trx_commit 这个参数设置成 1 的时候,表示每次事务的 redo log 都直接持久化到磁盘。这个参数建议你设置成 1,这样可以保证 MySQL 异常重启之后数据不丢失。

binlog(归档日志)

binlog 是 server 层的日志,所有的存储引擎共享。binlog 一般用来做数据库的主从复制或恢复数据库。

只有开启了 binlog 和 binlog 两份日记才有 crash-safe 功能 为什么 binlog 不能做到 crash-safe? 有两个决定性的原因:

  1. 历史上的原因是,这个是一开始就这么设计的,所以不能只依赖binlog。
  2. 操作上的原因是,binlog是可以关的,你如果有权限,可以 set sql_log_bin=0 关掉本线程的binlog日志。 所以只依赖binlog来恢复就靠不住。

事务提交需要两个阶段,prepare 阶段和 commit 阶段。写redolog,redolog 日志标志一下状态(prepare状态)–>写 binlog –>写完提交事务并将 redolog 标记成commit,commit 就是将 redolog 真正写入日志磁盘,这个操作成为二段提交,在很多分布式系统中都有类型的模式。

假如只有 binlog,有可能先提交事务再写 binlog,有可能事务提交数据更新之后数据库崩了,还没来得及写 binlog。这样就导致主从数据库不一致或者无法恢复数据库了。

即使先写 binlog 再提交事务更新数据库,还是有可能写binlog成功之后数据库崩掉而导致数据库更新失败,这样也会导致主从数据库不一致或者无法恢复数据库。所以只有 binlog 做不到 crash-safe。为了支持 crash-safe,需要 redolog,而且为了保证逻辑一致。

sync_binlog 这个参数设置成 1 的时候,表示每次事务的 binlog 都持久化到磁盘。这个参数建议设置成 1,这样可以保证 MySQL 异常重启之后 binlog 不丢失。

两个日志区别

主要区别:

  1. redo log 和 binlog 有一个很大的区别就是,一个是循环写,一个是追加写。也就是说 redo log 只会记录未刷盘的日志,已经刷入磁盘的数据都会从 redo log 这个有限大小的日志文件里删除。binlog 是追加日志,保存的是全量的日志。
  2. redo log 是 InnoDB 引擎特有的,binlog 是 MySQL 的 Server 层实现的,所有引擎都可以使用。
  3. redo log 是存储引擎自带的日志,经过了分析器、执行器等一系列的过程,直接接触数据,所以是记录的是“在某个数据页上的修改”,而binlog是server层的日志,抽象层级更高,所以记录的是SQL语句的原始逻辑。或者理解为 redo log记录的是x页做了x修改,binlog记录的是给x行做了x更新。 redo log是物理的,binlog是逻辑的。

简单的 update 语句时的内部流程。

1
update T set c=c+1 where ID=2;
  1. 执行行器先找引擎取 ID=2 这一行。ID 是主键,引擎直接用树搜索找到这一行。如果 ID=2 这一行所在的数据页本来就在内存中,就直接返回给执行器;否则,需要先从磁盘读入内存,然后再返回。对于更新操作,并不会更新某条记录就把某条记录查询到内存中对其做修改就行,而是将对应记录所在页都加载到内存中。
  2. 执行器拿到引擎给的行数据,把这个值加上 1,比如原来是 N,现在就是 N+1,得到新的一行数据,再调用引擎接口写入这行新数据。 数据的算术运算,非索引列的条件过滤等都是在 server 层完成的
  3. 引擎将这行新数据更新内存中数据所在的数据页,同时将这个更新操作记录到 redo log 里面,此时 redo log 处于 prepare 状态。然后告知执行器执行完成了,随时可以提交事务。
  4. 执行器生成这个操作的 binlog,并把 binlog 写入磁盘。
  5. 执行器调用引擎的提交事务接口,引擎把刚刚写入的 redo log 改成提交(commit)状态,更新完成。

流程图:

结束

简单的学习了 Mysql 的日志写入策略,如果有错误请在评论区指出。


一条SQL更新语句的执行过程
http://example.com/posts/44605.html
作者
她微笑的脸y
发布于
2022年7月16日
许可协议