该系列文章借鉴丁齐《MySQL实战45讲》,作用是为了自我提升,加深理解和思考,也希望能和大家一起互动学习!
引子
本篇主要以日志系统为轴,sql更新为引子,希望能窥探出sql更新到底都牵动了谁的弦。
这里先分享工作小妙招,如果误删了部分数据,又没有备份。怎么办呢?要从生产捞数据一个个核对然后导入吗,这样虽然可以但完全没必要,有可能还会把别人辛辛苦苦造的测试数据给弄没了。oracle和mysql都有能恢复近期任一时间段内的功能;秘诀就是下面的sql。
--oracle的方式
select * from scott.dept as of timestamp to_timestamp('2011-12-09 10:00:00','yyyy-mm-dd hh24:mi:ss');
--mysql的方式
--可以利用undo日志的方式恢复,步骤比oracle复杂,但是网上教程颇多,这里意思一下,知道能依靠undo日志恢复就可以了
复制代码
先附上一条简单的更新sql
mysql> update T set c=c+1 where ID=2;
复制代码
如上更新sql,也会经过连接器、分析器、优化器、执行器;最终更新至存储引擎。
上述基本流程走完,更新还涉及两个重要模块redo log(重做日志)和 binlog(归档日志),这次我们就着重学习一下。
重要的日志模块:redo log
1.为什么要有redo log?
试想一下如果每次更新都要写进磁盘,意味着更新时得先查到对应数据,然后在更新。流量如果大一点,是不是IO压力非常大,而且效率也很低;所以先有个中间技术记录一下sql,也就是利用redo log,等压力很小的时候,在一条条的去做更新。这个技术叫做WAL(Write-Ahead Logging) ,也就是先写日志在写磁盘,目的减轻mysql更新时的压力。此时应该又产生一个疑问,那到查询时如何保证数据数据一致性呢?
2.redo log到底是个啥?
redo log是InnoDB引擎特有的;redo log出生是为了解决crash-safe(下面会针对这个特性详细描述);redo log是一个可配置大小的文件,比如可以配置为一组 4 个文件,每个文件的大小是 1GB。然后分配两个指针进行位置记录,从头开始写,写到末尾就又回到开头循环写,如下面这个图所示。
其中write pos是记录写指针位置;check point是消除记录的指针,也就是删除记录的日志;write pos和check point中间则是空余空间;当两个指针重合,则意味着日志已经写满了,就得靠check point”擦除”一些记录。有了 redo log,InnoDB 就可以保证即使数据库发生异常重启,之前提交的记录都不会丢失,这个能力称为 crash-safe。(快记,异常重启数据恢复解决方案),在抛几个问题,如何进行数据恢复?恢复数据量能达到多少呢?redo log中记录的是什么?欢迎大家留言讨论。
重要的日志模块:binlog
别被这个两个日志弄懵圈了,一定要记住MySQL基础架构分为Sever层和引擎层,redo log是引擎层中InnoDB特有的;binlog则是Server层中通用的。
1.什么是binlog?
binlog也称之为归档日志,想想游戏中的归档,一般单机游戏退出前都需要先存储归档,下次游戏时才能读取对应的档案继续爆肝,也就是恢复到游戏中某个时间点的作用,就大致推测出这个日志归档的作用。在温习一下redo log,网络游戏中无论你直接关闭,还是游戏闪退,再次连接上,都不会让你从1级重头开始的,而是续上继续耍。
2.为什么要有binlog?
binlog 是逻辑日志,记录的是这个语句的原始逻辑,比如“给 ID=2 这一行的 c 字段加 1 ”;binlog 是可以追加写入的。“追加写”是指 binlog 文件写到一定大小后会切换到下一个,并不会覆盖以前的日志。
整体逻辑在回首
我们再来看看一条更新语句的内部流程。
mysql> update T set c=c+1 where ID=2;
复制代码
1.执行器先找引擎取 ID=2 这一行。ID 是主键,引擎直接用树搜索找到这一行。如果 ID=2 这一行所在的数据页本来就在内存中,就直接返回给执行器;否则,需要先从磁盘读入内存,然后再返回。
2.执行器拿到引擎给的行数据,把这个值加上 1,比如原来是 N,现在就是 N+1,得到新的一行数据,再调用引擎接口写入这行新数据。
3.引擎将这行新数据更新到内存中,同时将这个更新操作记录到 redo log 里面,此时 redo log 处于 prepare 状态。然后告知执行器执行完成了,随时可以提交事务。
4.执行器生成这个操作的 binlog,并把 binlog 写入磁盘。
5.执行器调用引擎的提交事务接口,引擎把刚刚写入的 redo log 改成提交(commit)状态,更新完成。
从上步骤中,可以看到redo log做了两次操作,prepare和commit,这就是大名鼎鼎的”两阶段提交”。
两阶段提交
redo log的两次操作,prepare和commit,细心的小伙伴可能发现binlog在这两个操作中间且由执行器写入。为何如此设计呢?设想一下如果不用二阶段提交,那么是不是可能出现只存了redo log或者binlog的情况,例如存储过程中宕机等等。此时就会出现两个文本存储的数据在逻辑上不一致的情况。
在来看看两阶段提交的神奇地方,如若过程中宕机,无论该条语句是否已经写入binlog,只要redo log中prepare和commit没形成一个闭环,那么该次提交的事务就不成功,数据也会被删除。两阶段提交就让这两个日志状态保持了逻辑上的一致。
越学越越感到自己的弱小,时刻对学术保持敬畏之心,望共勉。欢迎加我VX一起互动学习!
VX号:CXYWG1943132157
最后希望大家能留下一个小赞??♀️