了解了 SQL 执行的流程,知道每一条语句都经过连接器、查询存储、分析器、优化器、执行器最后到存储引擎的过程。查询语句是如此,更新语句也不例外。
不同的是,更新语句会修改表数据,这里就涉及到两个重要的日志模块 redolog 和 binlog。
本篇还是选用 InnoDB 搜索引擎。
下面引入丁奇的经典比喻。
- 《孔乙己》这篇文章中,酒店掌柜有一个粉板,专门用来记录客人的赊账记录。
- 如果赊账的人不多,他可以把顾客名和账目写在板上。
- 但如果赊账的人多了,粉板总会有记不下的时候,这个时候掌柜一定还有一个专门记录赊账的账本。
-
- 如果有人要赊账或者还账的话,掌柜一般有两种做法:
- 一是直接把账本翻出来,把这次赊的账加上去或者扣除掉;
- 另一种是先在粉板上记下这次的账,等打烊以后再把账本翻出来核算。
-
- 在生意红火柜台很忙时,掌柜一定会选择后者,因为前者操作实在是太麻烦了。
- 首先,你得找到这个人的赊账总额那条记录。你想想,密密麻麻几十页,掌柜要找到那个名字,可能还得带上老
- 花镜慢慢找,找到之后再拿出算盘计算,最后再将结果写回到账本上。
-
- 你想想,如果掌柜没有粉板的帮助,每次记账都得翻账本,效率是不是低得让人难以忍受?
上面的比喻非常形象地说明了 Mysql 中的 WAL(Write-Ahead-Logging) 技术。
先写日志(redolog),再写磁盘。也就是先写粉板再写账本。
redolog 是 InnoDB 引擎特有的日志。
当有数据要更新时,InnoDB 会先记录 redolog,同时更新内存,更新操作就结束了。等到合适的时候(空闲或者粉板写满了),再更新磁盘。
redolog 有固定大小,就像固定大小的粉板,写满就得回到开头循环写。
write pos 是当前记录粉板的位置,一边写一边往后移。
check point 是擦除的位置,擦除前会把更新记录到数据文件。
redolog 保证了数据库异常重启时数据不丢失,这个能力被称为 crash-safe。

(丁奇原图)
- mysql> show variables like "innodb_log_group_home_dir";
- +---------------------------+-------+
- | Variable_name | Value |
- +---------------------------+-------+
- | innodb_log_group_home_dir | ./ |
- +---------------------------+-------+
- 1 row in set (0.00 sec)
- mysql> show variables like "innodb_log_file%";
- +---------------------------+----------+
- | Variable_name | Value |
- +---------------------------+----------+
- | innodb_log_file_size | 33554432 |
- | innodb_log_files_in_group | 2 |
- +---------------------------+----------+
- 2 rows in set (0.00 sec)
- mysql> show variables like "innodb_flush_log_at_trx_commit";
- +--------------------------------+-------+
- | Variable_name | Value |
- +--------------------------------+-------+
- | innodb_flush_log_at_trx_commit | 1 |
- +--------------------------------+-------+
- 1 row in set (0.00 sec)
binlog 是 Server 层的日志,叫归档日志。不同于 redolog 只有 InnoDB 有,binlog 任何引擎都可以使用。
binlog 是逻辑日志,记录语句的原始逻辑;redolog 是物理日志,记录数据页的修改内容。
binlog 不会循环写,一个写完后切换到写一个;redolog 有固定的大小。
binlog 在 data 目录下,mysql-bin.xxxxxx。
设置 binlog 每次事务直接持久化到磁盘上,保证重启后数据不丢失。
- mysql> show variables like "sync_binlog";
- +---------------+-------+
- | Variable_name | Value |
- +---------------+-------+
- | sync_binlog | 1 |
- +---------------+-------+
- 1 row in set (0.00 sec)
update T set c=c+1 where ID=2;
执行器调用引擎接口找 ID=2 的数据,如果 ID=2 数据所在的数据页在内存中,则直接返回给执行器。否则先从磁盘读取再返回。
执行器拿到数据,把 c 加 1,得到新的一行数据。然后调用引擎接口,写入这行数据。
引擎将新数据更新到内存中,顺便记录 redolog,此时 redolog 处于 prepare 状态,然后告诉执行器可以提交事务。
执行器把 binlog 写入磁盘,然后调用引擎接口提交事务。redolog 状态变为 commit。更新完成。
既然记录了 log,那么当数据误操作后,就可以根据数据库定期备份,加上记录的 log 方便的恢复到误操作之前的状态。
先根据备份恢复到临时表
根据 binlog 找到备份后到误操作前的记录。
把恢复好的临时库按需要同步到正式库中。
只要有 log,就可以恢复到任何时刻的数据状态。
再也不用担心误操作无法恢复了。