#mysql# 在之前的MySQL 查询期间会发生什么这篇文章中,深入研究了MySQL中执行查询语句的过程,重点介绍了执行过程中涉及的完整处理模块。已经了解了查询语句的执行过程通常会遍历连接器、解析器、优化器、执行器等功能模块,最终到达存储引擎。
这篇文章阐述的是MySQL的update语句的执行流程。
下面是一个表的建表语句,该表具有一个主键 id 和一个整数字段 age:
sql
create table T(id int primary key, age int);
如果需要增加 id 等于 1 的行中的值,SQL写法
bash
update T set age = age + 1 where id = 1;
首先,可以肯定地说,更新语句也将执行查询语句中使用的相同处理顺序。
在前面的文章中,说到当对表进行更新时,与该表关联的查询缓存将变得无效。因此,该更新语句导致清除与表 T 有关的所有缓存结果。这就是不建议使用查询缓存的原因。随后,分析器通过词法和句法解析,将其识别为更新语句。然后优化器确定使用到id 列索引进行数据的更新。此后,executor执行器承担实际执行的责任,确定要修改的相关行。
与查询过程相比,更新过程涉及两个关键的日志记录模块:redolog 和binlog。
Redo log
在 MySQL 中,一个很大的挑战就是,如果每次更新操作都需要进行磁盘写入,然后在磁盘上找到相应的记录进行修改,则整个过程会产生大量的 I/O 和检索成本。
为了解决这个问题,MySQL 采用了预写日志记录,意思就是需要在写入磁盘之前进行日志记录。
具体来说,当一条记录需要更新时,InnoDB 引擎首先将该记录写入redo log(重做日志)并更新内存中的状态。至此,更新过程就被认为完成了。同时,在适当的时候,InnoDB 引擎通常会在系统空闲期间将操作的记录更新到磁盘。
如果某一天的大量记录更新导致重做日志达到其容量,MySQL 将面临操作暂停。在此期间,重做日志中的部分记录将被分派到磁盘进行更新。随后,从重做日志中清除相关记录以腾出空闲空间。
因此,InnoDB中的重做日志是固定大小的。例如,它可以配置为一组四个文件,每个文件大小为 1GB,从而为文档操作提供 4GB 的累积容量。从起点开始一直到终点,该过程是从头开始,循环的写入模式。
write pos 指定当前记录位置,随着写入的进行而逐渐前进。当到达第三个文件时,该过程从第一个文件的开始处重新开始。同时,check point表示将要清除数据的当前位置。这也是循环往复的。在删除记录之前,数据文件中的条目会被更新。
write pos 和check point之间的空置区域是容纳新操作的存储位置。
如果写入位置赶上检查点,则表明空间已满。此时,必须暂时暂停任何新的更新操作进一步执行,从而需要清除选定的数据以向前推进检查点。
由于重做日志的存在,即使数据库因异常而突然重启,因为InnoDB 保留了先前提交的记录,也能对数据进行恢复。
Binlog
从 MySQL 的架构来看,它本质上包含两个领域:服务器层 ,主要涉及 MySQL 的功能方面,以及存储引擎层,负责具体的数据存储的问题。
正如上面说的,重做日志是 InnoDB 引擎的特殊日志。同样,在Server层内部,也有自己的日志,称为binlog。
为什么需要两组日志?
这是因为MySQL最初并没有整合InnoDB引擎。MySQL 的原生引擎是 MyISAM,但 MyISAM 没有崩溃恢复功能。binlog只能用作存档。
InnoDB 是由另一家公司作为插件引入 MySQL 的,它的集成就是为了解决这个问题。由于仅靠 binlog不能提供崩溃安全能力,InnoDB 引入了一个独特的日志系统,即redo log来建立崩溃恢复能力。
这两类日志有以下区别:
- redo log是InnoDB引擎独有的,而binlog是MySQL中的服务器层实现,所有引擎都可以使用。
- redo log功能充当物理日志,记录"对特定数据页进行了哪些修改"。相比之下,binlog充当逻辑日志,记录"该语句的原始逻辑"。
- redo log采用循环的书写方式,不可避免地会耗尽其固定空间。而binlog仅附加写入。这意味着当一个binlog文件达到一定大小时,它会切换到下一个,而不会覆盖以前的日志。
对这两个日志有了概念性的理解后,让我们深入研究执行器和 InnoDB 引擎在执行更新语句时发生的内部事件序列。
流程
- 执行器通过请求引擎检索id=2的数据。鉴于id是主键,存储引擎采用基于树的搜索来定位相关行。如果包含id=2的数据页在内存中已经存在,则立即将其给到执行器。或者,如果数据页在磁盘上,则必须先将其提取到内存中,然后再传输回执行器。
- 执行器从引擎接收到行数据后,将该值加 1,将原始值转换N为N+1。这会产生一组新的行数据,然后通过其接口将其提供给引擎以写入这些新数据。
- 引擎将这些新的行数据集成到内存中,同时记录在redo log.此时,redo log就进入了prepare状态。随后,引擎通知执行器执行完成,这次事务随时准备提交。
- 执行器将该操作的对应内容记录biglog并将其刻录到磁盘上。
- 执行器调用引擎的事务提交接口,提示引擎将之前写入的重做日志修改为commit状态,从而完成更新。
重做日志的写入分为两步:prepare和commit,为什么?
将重做日志写入分为"准备"和"提交"两个步骤有特定的目的。这种机制被称为"两阶段提交协议",它增强了事务的可靠性和持久性。
- 在"准备"阶段,事务所做的更改会记录在重做日志中,但尚未应用于内存中的实际数据。这允许验证和确认事务可以在不影响数据完整性的情况下提交。
- 在"提交"阶段,更改正式应用于数据,并且事务的重做日志条目被标记为"提交",表明该事务现在是永久性的,并且可以在崩溃时恢复。
这种两阶段方法确保只有在安全地记录和验证事务的更改后才提交事务,从而有助于数据库的整体一致性和持久性。
总结
MySQL 中更新语句的执行涉及几个关键步骤和日志机制。
MySQL通过redo logs和binlogs保证数据的一致性、持久性和可恢复性。
Redo log记录物理修改,确保引擎级数据恢复,同时binlog记录逻辑操作,便于复制和数据同步。
恢复数据时,结合备份binlog可以实现精确的时间点恢复。
这种深入的理解有助于有效地管理和维护 MySQL 数据库。