MySQL(五)简单更新语句流程
目录
¶概述
首先从创建一个表的一条更新语句开始,下面是这个表的创建语句,这个表有一个主键 ID 和 一个整形字段 C
1 | mysql> create table T(ID int primary key, c int); |
⛵ 查询语句经过的流程,更新语句同样也会经历一遍。
- 在执行语句之前要先连接数据库,这就是连接器的工作;
- 在一个表上进行更新操作时,跟这个表有关的查询缓存会失效,所以这条语句就会把表 T 上所有缓存结果都清空;
- 分析器会通过词法和语法解析知道当前是一条更新语句。优化器觉定使用 ID 这个索引
- 最终,执行器负责具体执行,查询到这一行,然后进行更新操作
¶重做日志(redo log
)
❓ 在 MySQL 里存在这样一个问题,如果当前系统是 I/O 密集型的,那么一条更新语句是否真的在物理磁盘上进行更新了?
为了解决这个问题,MySQL 设计者利用了日志相关技术,提高数据库更新效率。这种技术就是 MySQL 里经常说到的 WAL (Write Ahead Logging
) 技术,它的关键点就是先写日志,再写磁盘,也就是在系统资源占用紧张的时候,先记录日志,直到系统资源不紧张时再进行持久化操作。
具体来说,当有一条记录需要更新的时候,InnoDB 引擎就会先把记录写到redo log
里面,并更新内存,这个时候更新就算完成了。接下来,InnoDB 引擎会在适当的时候,将这个操作记录更新到磁盘里面,而这个更新往往是在系统比较空闲的时候做
❓ 但是如果系统一直处于忙碌的状态,redo log 中的内容就会不断的增长,这时候应该怎么办?
✨ Write pos
是当前记录的位置,会一边写一边后移,一旦写到 3 号文件末尾,下一次就回到 0 号文件开头继续写。checkpoint
是一个备份点,表示之前的内容已经持久化到物理磁盘中,并且它也是循环往后推移的
write pos 和 checkpoint 之间的内容是空白的,表示可以继续记录新操作的条目;如果 write pos 追上 checkpoint,表示 redo log 日志记满了,这时候不能再执行新的更新,需要停下来先持久化一些记录,把 checkpoint 推进一部分。
¶归档日志(binlog
)
MySQL 整体就分为两块:一块是 Server 层,它主要做的是 MySQL 功能层面的事情;还有一块是引擎层,负责存储相关的具体事宜。上面描述的 redo log 是 InnoDB 引擎特有的日志,而 Server 层也有自己的日志,称为 binlong
❓ 为什么会有两份日志?
🆚 MySQL 中 中的 binlog(二进制日志)也可以记录写操作并用于数据的恢复,但 bin log 与 redo log 是有着根本的不同的
binlog 记录日志有两种模式,statement 格式以及 row 格式
- Statement 格式,记录的日志是执行器执行的每一条 sql 语句
- row 格式会记录行的内容,包括两条语句,分别是更新前和更新后的内容(通常采用的是这种模式)
¶update 内部流程
1 | mysql> update T set c=c+1 where ID=2; |
⛵ 有了对这两个日志的概念性理解,再来看执行器和 InnoDB 引擎在执行这个简单的 Update 语句的内部流程
下图给出了 update 语句的执行流程图,浅色框表示是在 InnoDB 内部执行的,深色框表示是在执行器中执行的
最后事务提交的过程可能有点「绕」,将 redo log 的写入拆成了 两个步骤:prepare
和commit
,这就是「 两阶段提交 」
¶两阶段提交
❓ 如何让数据库进行回滚,例如:让数据库恢复到半个月内任意一秒到状态?
当需要恢复到指定的某一秒时,比如某天下午两点发现中午十二点有一次误删表,需要找回数据,那你可以这么做:
- 首先,找到最近的一次全量备份,如果你运气好,可能就是昨天晚上的一个备份,从这个备份恢复到临时库;
- 然后,从备份的时间点开始,将备份的 binlog 依次取出来,重放到中午误删表之前的那个时刻
此时,临时库就跟误删之前到线上库一样了,就可以把表数据从临时库取出来,按需要恢复到线上库中
❓ 为什么日志需要两阶段提交?
仍然用前面的 update 语句来做例子。假设当前 ID=2 的行,字段 c 的值是 0,再假设执行 update 语句过程中在写完第一个日志后,第二个日志还没有写完期间发生了 crash,会出现什么情况呢?
- 先写 redo log 后写 binlog。假设在 redo log 写完,binlog 还没有写完的时候,MySQL 进程异常重启。由于我们前面说过的,redo log 写完之后,系统即使崩溃,仍然能够把数据恢复回来,所以恢复后这一行 c 的值是 1。但是由于 binlog 没写完就 crash 了,这时候 binlog 里面就没有记录这个语句。因此,之后备份日志的时候,存起来的 binlog 里面就没有这条语句。然后你会发现,如果需要用这个 binlog 来恢复临时库的话,由于这个语句的 binlog 丢失,这个临时库就会少了这一次更新,恢复出来的这一行 c 的值就是 0,与原库的值不同
- 先写 binlog 后写 redo log。如果在 binlog 写完之后 crash,由于 redo log 还没写,崩溃恢复以后这个事务无效,所以这一行 c 的值是 0。但是 binlog 里面已经记录了「 把 c 从 0 改成 1 」这个日志。所以,在之后用 binlog 来恢复的时候就多了一个事务出来,恢复出来的这一行 c 的值就是 1,与原库的值不同。
通过上面两个例子就可以说明,如果不使用「 两阶段提交 」,那么数据库的状态就有可能和用它的日志恢复出来的库的状态不一致
简而言之,redo log 和 bin log 都可以用于表示事务的提交状态,而两阶段提交就是让这两个状态保持逻辑上的一致。
¶总结
✨ 本文介绍了 MySQL 中最重要的两个日志,即物理日志 redo log 和逻辑日志 bin log