侧边栏壁纸
  • 累计撰写 244 篇文章
  • 累计创建 16 个标签
  • 累计收到 0 条评论
隐藏侧边栏

SQL 更新语句的执行流程与日志写入

kaixindeken
2021-04-26 / 0 评论 / 0 点赞 / 68 阅读 / 2,486 字

执行流程

SQL 更新语句包括插入、修改和删除三种操作,我们还是基于上篇教程中的 MySQL 服务端架构图进行介绍,这样会更加直观一些:

1.jpeg

和 SQL 查询语句一样,MySQL 客户端提交 SQL 更新语句(表示一个更新请求)前,先要通过连接器建立与服务端的连接,然后就可以执行更新操作了(假设在 test 数据库中已经存在一个 post 数据表,如果没有的话,可以手动创建):

1.jpeg

当一张数据表有更新操作时,对应的查询缓存数据会清空,所以上述插入语句会清空 post 表的所有缓存(修改、删除语句也是一样)。

接下来,分析器会通过词法和语法解析知道这是一条 SQL 插入语句,优化器为其生成对应的执行计划,最后,执行器负责具体执行,插入数据(具体操作交由存储引擎去做)。

以上插入语句,如果是像下面这样的修改语句:

update post set title = 'test title 2' where id = 1;

连接器和查询缓存这里和插入语句都是一样的,在分析器中会解析出这是一条 SQL 修改语句,由于带有 WHERE 查询条件,因此在优化器生成的执行计划会判定是否使用索引(我们可以通过explain 语句预览执行计划,这里可以看到会使用 id 这个主键索引):

1.jpeg

最后,执行器负责具体执行,找到这一行记录,然后进行更新。

与查询流程不一样的是,更新流程还涉及两个重要的日志写入,分别是 redo log(重做日志)和 binlog(归档日志)。

日志写入

MySQL 数据库数据是会持久化到磁盘的,如果每一次的更新操作都要写入磁盘,整个过程的 IO 成本很高(如果包含查询的话,还有额外的查询成本)。

为了解决这个问题,MySQL 的设计者引入了 WAL 技术(Write-Ahead Logging),即先写日志,再写磁盘。

以 InnoDB 引擎为例,当有一条记录需要更新的时候,InnoDB 引擎就会先把记录写到 redo log(重做日志)里面,并更新内存,这个时候更新就算完成了,然后,InnoDB 引擎会在系统空闲的时候,将这个操作记录更新到磁盘里面。

之所以叫做重做日志,是因为如果 MySQL 数据库服务端发生异常崩溃,重启时可以根据这个日志记录的步骤完成事务已提交但未持久化到磁盘的数据更新操作,从而保证事务的持久性。

那为什么又有 binlog 呢?

实际上,redo log 是 InnoDB 引擎提供的日志系统,在 InnoDB 引擎出现之前,MySQL 默认的存储引擎是 MyISAM,那个时候为了实现数据备份和恢复,使用的是 binlog,不过 binlog 是一个归档日志,不具备数据库崩溃重启后的数据恢复功能,不过 MyISAM 也不支持事务,而 InnoDB 支持事务,因此,InnoDB 专门开发了一套 redo log 日志系统。

作为归档日志,binlog 是增量写(可以一直追加写入),负责数据库全量数据的备份和恢复,比如数据库集群中的主从同步就是基于 binlog 实现的。

作为重做日志,redo log 负责已提交事务未持久化到磁盘部分更新数据的备份和恢复(重新做一遍),是 InnoDB 引擎事务机制下数据恢复的重要补充,也是事务持久性的保障,如果 redo log 中的数据已经持久化到磁盘,这部分重做日志就可以被覆盖了,所以 redo log 是循环写(后面的记录会覆盖前面的)。

注:binlog 是属于 MySQL Server 层的日志系统,因此所有的存储引擎都可以共用它。

下面我们来看看在 InnoDB 引擎中,这两个日志是如何写入的:

  1. 执行器通过 API 接口将更新数据传递给存储引擎执行更新操作;
  2. 存储引擎在拿到更新数据后,先将其更新到内存,同时将这个更新操作记录到 redo log,此时 redo log 处于 prepare 状态,然后告知执行器执行完成了,随时可以提交事务;
  3. 执行器生成这个操作的 binlog,并把 binlog 写入磁盘(写入时机可以配置,对于事务操作而言,都是在事务提交时才会持久化写入的,相关细节我们后面讲数据库数据一致性的时候会详细介绍);
  4. 执行器调用引擎的提交事务接口,引擎把刚刚写入的 redo log 改成 commit 状态,更新完成。

1.png

在上述步骤中,将 redo log 的写入拆成了两个步骤:prepare 和 commit,这就是「两阶段提交」。

如果不使用两阶段提交,会导致两份日志恢复的数据不一致:比如先写 redo log,binlog 还没有写入,数据库崩溃重启;或者先写 binlog,redo 还没有写入数据库崩溃重启,都将造成恢复数据的不一致。

而使用两阶段提交后,就可以保证两份日志恢复的数据一致:只有 binlog 写入成功的情况下,才会提交 redo log,否则 redo log 处于 prepare 状态,事务会回滚,这样一来,就保证了数据的一致性。

0

评论区