MySQL 作为一种广泛使用的关系数据库管理系统,支持事务处理。本文将介绍 MySQL 在执行 UPDATE 语句的整个流程。
具体更新一条记录 UPDATE t_user SET age = 18 WHERE id = 1; 的流程如下:
将数据加载到 buffer pool什么是 buffer poolMySQL 的数据是存储在磁盘里的,但是也不能每次都从磁盘里面读取数据,这样性能是极差的。为此,Innodb 存储引擎设计了一个缓冲池(Buffer Pool) ,来提高数据库的读写性能。
有了缓冲池后:
当读取数据时,如果数据存在于 Buffer Pool 中,客户端就会直接读取 Buffer Pool 中的数据,否则再去磁盘中读取。当修改数据时,首先是修改 Buffer Pool 中数据所在的页,然后将其页设置为脏页,最后由后台线程将脏页写入到磁盘。执行过程在 MySQL 启动的时候,InnoDB 会为 Buffer Pool 申请一片连续的内存空间,然后按照默认的16KB的大小划分出一个个的页, Buffer Pool 中的页就叫做缓存页。此时这些缓存页都是空闲的,之后随着程序的运行,才会有磁盘上的页被缓存到 Buffer Pool 中。
执行器负责具体执行,会调用存储引擎的接口,通过主键索引树搜索获取 id = 1 这一行记录:
如果 id=1 这一行所在的数据页本来就在 buffer pool 中,就直接返回给执行器更新;如果记录不在 buffer pool,将数据页从磁盘读入到 buffer pool,返回记录给执行器开启事务执行过程执行器得到聚簇索引记录后,会看一下更新前的记录和更新后的记录是否一样:
如果一样的话就不进行后续更新流程;如果不一样的话就把更新前的记录和更新后的记录都当作参数传给 InnoDB 层,让 InnoDB 真正的执行更新记录的操作,然后开启事务。记录 undo log 到 buffer pool什么是 undo logundo log 是一种用于撤销回退的日志。在事务没提交之前,MySQL 会先记录更新前的数据到 undo log 日志文件里面,当事务回滚时,可以利用 undo log 来进行回滚,它保证了事务的 ACID 特性 中的原子性(Atomicity) 。
Buffer Pool 除了缓存「索引页」和「数据页」,还包括了 undo 页,插入缓存、自适应哈希索引、锁信息等等。
undo log 是逻辑日志,也就是与原操作相反的语句。undo log 除了实现事务回滚之外,还维护一个版本链,用于实现 MVCC。
执行过程InnoDB 层更新记录前,首先要记录相应的 undo log,因为这是更新操作,需要把被更新的列的旧值记下来,也就是要生成一条 undo log,undo log 会写入 Buffer Pool 中的 Undo 页面。
undo log 的刷盘时机undo log 和数据页的刷盘策略是一样的,都需要通过 redo log 保证持久化。这点稍后详细介绍。
undo log 的销毁时机undo log 在事务执行时产生。事务提交时,如果没有并发事务,会删除 undo log。在有并发事务的情况下,不会立即删除 undo log,因为这些日志可能还用于 MVCC。undo log 的销毁时机与其他事务有关,在可重复读的情况下,当所有活跃事务都不再需要访问这些旧版本数据时,会删除 undo log。因此,在长事务的情况下,undo log 会变得很大。
在 buffer pool 中更新数据执行过程引入了 Buffer Pool 后,当修改数据时,首先是修改 Buffer Pool 中数据所在的页,然后将其页设置为脏页,但是磁盘中还是原数据。
因此,脏页需要被刷入磁盘,保证缓存和磁盘数据一致,但是若每次修改数据都刷入磁盘,则性能会很差,因此一般都会在一定时机进行批量刷盘。
数据的刷盘时机下面几种情况会触发脏页的刷新:
当 redo log 日志满了的情况下,会主动触发脏页刷新到磁盘;Buffer Pool 空间不足时,需要将一部分数据页淘汰掉,如果淘汰的是脏页,需要先将脏页同步到磁盘;MySQL 认为空闲时,后台线程会定期将适量的脏页刷入到磁盘;MySQL 正常关闭之前,会把所有的脏页刷入到磁盘;记录 redo log 到 redo log buffer什么是 redo log为了防止断电导致数据丢失的问题,当有一条记录需要更新的时候,InnoDB 引擎就会先更新内存(同时标记为脏页),然后将本次对这个页的修改以 redo log 的形式记录下来,这个时候更新就算完成了。
后续,InnoDB 引擎会在适当的时候,由后台线程将缓存在 Buffer Pool 的脏页刷新到磁盘里,这就是 WAL (Write-Ahead Logging)技术。WAL 技术指的是, MySQL 的写操作并不是立刻写到磁盘上,而是先写日志,然后在合适的时间再写到磁盘上。redo log 是物理日志,记录了某个数据页做了什么修改。
在事务提交时,只要先将 redo log 持久化到磁盘即可,可以不需要等到将缓存在 Buffer Pool 里的脏页数据持久化到磁盘。当系统崩溃时,虽然脏页数据没有持久化,但是 redo log 已经持久化,接着 MySQL 重启后,可以根据 redo log 的内容,将所有数据恢复到最新的状态。
执行过程在内存修改该 Undo 页面或者更新内存中的记录后,需要将内容记到 redo log 中。
执行一个事务的过程中,产生的 redo log 也不是直接写入磁盘的,因为这样会产生大量的 I/O 操作,而且磁盘的运行速度远慢于内存。所以,redo log 也有自己的缓存—— redo log buffer,每当产生一条 redo log 时,会先写入到 redo log buffer,后续在持久化到磁盘。redo log buffer 独立于 buffer pool。
至此,一条记录更新完了。
redo log buffer 的刷盘时机主要有下面几个时机:
MySQL 正常关闭时;当 redo log buffer 中记录的写入量大于 redo log buffer 内存空间的一半时,会触发落盘;InnoDB 的后台线程每隔 1 秒,将 redo log buffer 持久化到磁盘。每次事务提交时都将缓存在 redo log buffer 里的 redo log 直接持久化到磁盘。记录 binlog 到 binlog cache什么是 binlogMySQL 在完成一条更新操作后,Server 层还会生成一条 binlog,等之后事务提交的时候,会将该事物执行过程中产生的所有 binlog 统一写 入 binlog 文件。
binlog 文件是记录了所有数据库表结构变更和表数据修改的日志,不会记录查询类的操作,比如 SELECT 和 SHOW 操作。
执行过程在一条更新语句执行完成后,然后开始记录该语句对应的 binlog,此时记录的 binlog 会被保存到 binlog cache,并没有刷新到硬盘上的 binlog 文件,在事务提交时才会统一将该事务运行过程中的所有 binlog 刷新到硬盘。
binlog 刷盘时机事务执行过程中,先把日志写到 binlog cache(Server 层的 cache),事务提交的时候,再把 binlog cache 写到 binlog 文件中。
在事务提交的时候,执行器把 binlog cache 里的完整事务写入到 binlog 文件中,并清空 binlog cache。
两阶段提交什么是两阶段提交事务提交后,redo log 和 binlog 都要持久化到磁盘,但是这两个是独立的逻辑,可能出现半成功的状态,这样就造成两份日志之间的逻辑不一致。MySQL 使用「两阶段提交」来解决这个问题。
在 MySQL 的 InnoDB 存储引擎中,开启 binlog 的情况下,MySQL 会同时维护 binlog 日志与 InnoDB 的 redo log,为了保证这两个日志的一致性,MySQL 使用了内部 XA 事务。当客户端执行 commit 语句或者在自动提交的情况下,MySQL 内部开启一个 XA 事务,分两阶段来完成 XA 事务的提交。
执行过程事务的提交过程有两个阶段,就是将 redo log 的写入拆成了两个步骤:prepare 和 commit,中间再穿插写入 binlog,具体如下:
prepare 阶段:将 XID(内部 XA 事务的 ID) 写入到 redo log,同时将 redo log 对应的事务状态设置为 prepare,然后将 redo log 持久化到磁盘;commit 阶段:把 XID 写入到 binlog,然后将 binlog 持久化到磁盘,接着调用引擎的提交事务接口,将 redo log 状态设置为 commit。以上就是一条更新语句完整的执行流程,其中涉及了 undo log、redo log、binlog、buffer pool、两阶段提交这些知识点。通过梳理将整个知识点串起来,有助于更好地理解。
作者:掘掘子链接:https://juejin.cn/post/7385776177716527130