请支持正版:MySQL实战45讲
假如有一个表的创建语句,这个表有一个主键ID和一个整型字段c:
mysql>create table T (ID int primary key, c int);
如果将ID = 2这一行的值 + 1,SQL语句就会这么写
mysql>update T set c = c + 1 where id = 2;
上一篇文章01|基础架构:一条SQL查询语句是如何执行的还是会执行一遍:
与查询流程不一样的是,更新流程还涉及到两个重要的日志模块:redo log
和binlog
孔乙己文章中,酒店老板有一个板子,专门用来记录客人的赊账记录,如果赊账的人不多,那么他就可以把顾客名和账目写在板上,如果赊账的人多了,板子记录不下的时候,掌柜还有一个专门记录赊账的账本
如果有人要赊账或者还账的话,掌柜有两种做法:
如果生意红火,那么掌柜肯定选择后者,因为前者操作麻烦,首先要找到这个人的赊账总额的那一条记录,密密麻麻几十页,掌柜要找到那个名字,然后再拿算盘计算,最后再把结果写回账本上
相比之下,还是先在板子上记录一下来的更方便
同样的,MySQL里也有这个问题。如果每次更新操作都要写进磁盘,那么磁盘也得找到对应的那条记录,然后再更新,整个IO过程,查找成本都很高,为了解决这个问题,MySQL的设计者就采用了类似板子
的做法
板子和账本配合的过程其实就是MySQL里经常说到的WAL技术
,WAL关键的就是先写日志,再写磁盘,也就是先写板子,不忙的时候再写账本
具体来说就是:当有一条记录需要更新的时候,InnoDB引擎就会先把记录写到redo log中,并且更新内存,这个时候更新就算完成了。InnoDB会在合适的时候把这个操作更新到磁盘里,这个更新往往是在系统比较空闲的时候做的
值得注意的是,如果板子满了,掌柜只好先放下手头的工作,把板子里的一部分赊账记录更新到账本里,然后把这些记录从板子上擦掉,为记新帐腾出空间
与此类似的,InnoDB的redo log是固定大小的,比如可以配置一组4个人家,每个文件的大小是1GB,那么这块板子的记录就可以记录4GB的操作,从头开始写,写到末尾又回到开头循环写
MySQL从整体来看,其实就有两块:一块是Server层,它主要做的是MySQL功能层面的事情,还有一块是引擎层,负责存储具体相关的事宜。上面我们聊到的板子redo log是InnoDB特有的日志,Server层也有自己的日志,也就是binlog
那么有一个显然的问题:为什么有两份日志?
这两种日志有三点不同
在某个数据页上做了什么修改
,binlog是逻辑日志,记录的是这个语句的原始逻辑,比如说给ID = 2这一行的c字段加1现在有了基本的概念后,看看执行器和InnoDB引擎在执行这个简单的update语句时的内部流程
你可能注意到了,最后三步有点奇怪,很绕,这里就需要展开说说*两阶段提交**
为什么要有两阶段提交呢?
这是为了让两份日志之间的逻辑保持一致,在详细说明理由之前,要先说一下数据恢复的过程。现在假设你曾认真备份你的数据库
那么,现在你想回到都指定的某一秒时,你可以这样做:
这样你的临时库就和误删之前的线上的库就一样了,然后你可以把表数据哦那个临时库中取出来,按需要恢复到线上库里。
现在明白了数据恢复的过程,现在可以说明为啥要两阶段提交了,这里采用反证法证明
由于redolog和binlog是两个独立的逻辑,如果不用两阶段提交,那么就是先写redolog再写binlog或者反过来的顺序
下面我们看看会有什么问题:
依然使用前面update的语句作为例子,假设当前id = 2的行,c = 0,再假设执行update过程中在写完第一个日志后,第二个日志还没有写完期间发生了crash,会出现什么情况呢?
但是由于binlog没有写完,这时候binlog里可没有这一句,因此备份日志的时候,binlog就没有这条语句。然后如果你要用binlog来恢复临时库的话,由于这条语句的binlog丢失,那么这个临时库就少了一次更新,恢复出来的c值 = 0,与设想的不同
所以,可以看出,如果没有两阶段提交,那么数据库的状态可能和用它的日志恢复出来的库的状态是不一样的。
你可能会质疑这个事情发生的概率,平时也没有动不动就恢复数据库的场景
其实,不只是这一个场景要用到这个过程,比如说,当你需要扩容的时候,俨然就是需要多搭建一些备用库来增加系统的读能力的时候,现在常见的做法也是全量备份加上应用binlog来实现的,这个不一致
就会导致你的线上出现出从数据库不一样的qingk
最后,redolog和binlog都可以用于表示事务的提交状态,而两阶段提交就是让这两个状态保持逻辑上的一致