Mysql 百问系列: update 语句到底做了些什么?

1,226 阅读4分钟

问题:

一条简单的update 的语句到底牵扯了多少的东西?你能完全说明白吗?

来看看具体流程:

## student 只有id, 和name  两个字段,且只有id 一个主键,无其他索引。

update student set name = 'gxw' where id = 2
  1. 开启事务。
  2. Buffer Pool 中 查找id =2 所对应的数据。
  3. 如果在Buffer Pool中查找到了对应的数据,那么直接在Buffer Pool 中直接修改对应数据。如果没有找到,那么先从磁盘中找到对应数据,然后加载到Buffer Pool 中进行修改,修改前将原内容写入undo log
  4. 将更新的内容写入redo log
  5. 如果开启了binlog ,还会写入binlog日志。
  6. 事务提交进入prepare 阶段,将redo log 刷入磁盘
  7. 事务提交进入commit 阶段, 将binlog 输入磁盘

分析下每个阶段干了什么,为什么要这么做?

步骤1,mysql 默认配置每个单独的更新语句都会开启一个事务。所以上面这个语句第一步肯定是开启一个事务。
开启事务的同时,把原纪录写入undo log。 

为什么需要写undo log?
  • undo log 可以支持rollback(回滚)操作。我们知道如果人工开启一个事务,我们是可以手动调用RollBack 命令进行回滚事务的,当然如果允许事务过程中,服务器出现问题,mysql服务重启后也是可以进行自动回滚。而为了实现这个回滚功能,就需要用到undo log
  • Mysql 的MVCC (``多版本并发控制)也是通过undo log 来实现的。

步骤2和步骤3,在Buffer Pool 中查找数据并修改。 Buffer Pool 在这就不多解释了,我已经有专门的文章介绍,有兴趣的同学可以翻看我的其他文章。一句话概括就是问题避免过多的磁盘操作,增加的缓存。

步骤4, 将新内容写入redo log。

为什么需要redo log?
  • redo log 是为什么诞生呢? 我们说了Buffer Pool 是在内存中的,如果Mysql服务突然崩溃,或者服务器停电,那么放在Buffer Pool 中的数据就都会丢失。由于我们修改的时候只修改了Buffer Pool 中的内容,有部分内容修改了但还没往磁盘刷(所谓的脏页),为了避免脏页数据丢失,所以设计了redo log。详细了解redo log 机能也可查看我以往的文章。

步骤5,写binlog 。 相信很多人对binlog 比较熟悉,它可以开启,也可以关闭。它不影响事务的功能,但它提供了额外的功能,比如回溯的能力,比如你希望把数据库放回到2020-01-01 00:00:00 的时候,这时候就需要借助binlog了。 当然binlog 还有一个功能是实现主备同步。 Mysql 的 一主多从,双主多从等都是通过binlog 实现的。

步骤6,步骤7, 事务两阶段提交。(限制于篇幅详细不展开了,以后开专门的文章介绍)。 简单的说它是保证redo log 和 binlog 的一致性。当我们输入COMMIT 命令时,其实是经过了prepare 和commit 两个阶段的。

到这就介绍完一个最简单的更新语句的流程了。
如果再复杂点的情况呢?

当存在二级非唯一索引的时候

什么意思呢? 比如产品提了个需求说,我需要按照姓名来查找数据。为了提高搜索速度,我们可能就会为name 字段加上索引,由于姓名允许重复,那这个索引就不会被设置成唯一索引。假设索引名为index_name
我们知道index_name 的数据也会被存放在各个数据页中。
那么上面的修改语句,将id = 2 的name 修改为'gxw' 。那么除了修改数据页中的数据,index_name 的索引页中的数据也需要相应修改。
如果是你,你会怎么做?  从Buffer Pool 中的查找对应的index_name 中的数据,如果存在则修改,如果不存在,则到磁盘中查找数据加载到Buffer Pool中进行修改。可以看到这里需要一次磁盘操作,如果有多个二级非唯一索引那可能需要多次磁盘操作,影响效率。Mysql 的工程师为了优化,就又加入了change log
也就是说如果一个表中有二级非唯一索引,那么执行update 操作可能会用到change log

总结:

一个简单的更新语句,会涉及到的东西: undo log , Buffer Pool, redo log ,change log , binlog 。现在你可以不了解他们具体是如何工作的,但希望通过这篇文章你已经知道了他们到底是为了什么目的而存在的。