建表语句如下:
create table T(ID int primary key, c int);
更新的sql语句执行流程与查询的sql语句执行流程基本相同。
上一章说过,在一个表更新的时候,跟这个表有关的查询缓存就会失效,所以这条语句就会把表T上面所有的缓存结果清空,这就是为什么不推荐使用查询缓存的原因。
接下来。分析器会通过词法解析和语法解析来知道这是一条更新语句。优化器决定要使用ID这个索引。然后执行器负责具体执行,找到这一行,然后更新。
相比较于查询寻流程,更新流程多了两个重要的日志模块,redo log(重做日志)和binlog(归档日志)。
重要的日志模块redo log
更新的步骤如下:
先从磁盘中读取记录到内存,然后更新内存中的数据,再回写到磁盘,下次再更新就从磁盘继续查找。
如此会产生大量的IO操作,不仅仅会产生IO开销,也会产生大量的查找成本。
此时产生了MYSQL的WAL技术。也就是Write-Ahead Logging,它的关键点就是先写日志,再写磁盘。
此时一条记录需要更新的的时候。InnoDB引擎会将记录先写如redo log里面,并更新内存,这个时候更新操作就算完成了,同时在适当的时候InnoDBDB会将这个操作记录更新到磁盘里面,往往是在系统比较空闲的时候。
InnoDB的redo log是固定大小的,比如可以配置一组4个文件,每个文件的大小事1G,那么总共就可以记录4G的操作,从头开始写,写到末尾又回到开头重写,如下:
write pos是当前记录的位置,一边写一边往后移动,写到第三号文件末尾后就重写回到0号文件的开头,checkpoint是当然要擦除的位置,也是往后推移并且循环的,擦除记录前要把记录更新到数据库文件。
write pos和checkpoint质检室空着的部分,也已用来记录新的操作,如果weite pos=checkpoint的时候,表示redo log满了,无法再执行新的更新,得把一些记录清除(写入到磁盘),才能继续执行新的更新,即将checkpoint向前推进
有了redo log的另一个优势是,当mysql突然发生宕机,那么写入在内存中的数据来不及更新到磁盘的时候,即使mysql重启,之前提交的记录都不会丢失,这个能力被称为crash-safe。
重要的日志模块:binlog
上一章已经说明了mysql可以分为两大块,一块是Server层另一块是引擎层。前面的redo log是innodb存储引擎独有的日志,而server层也有自己的日志,称为binlog(归档日志)
最开始mysql存储引擎并没有InnoDB,那时候mysql的自带引擎是MyISAM,但是MyISAM并没有crash-safe能力,binlog日志只能用于归档(记录的是逻辑日志,如给id为2的c字段加1)
redo log和binlog有以下三种不同点:
- redo log是innoDB引擎独有的;binlog是mysql的Server层实现的,所有的引擎都可以使用
- redo log是物理日志,记录的是“在某个数据也上修改了什么”;binlog是逻辑日志,记录的是这个语句的原始逻辑,比如“”给id=2的这一行c字段加1“”。
- redo log是循环写入的,内存空间有限,而binlog是可以追加写入的,文件达到一定大小之后会切换到另一个,并不会覆盖以前的日志。
再看执行器和InnoDB引擎在执行update语句时的内部流程 - 执行器先通过引擎找到id =2这一行,ID是主键,因此执行使用树搜索找到这一行,如果ID=2这一行所在的数据页本来就在内存中,就直接返回给执行器,否则需要先从磁盘读入内存,再返回
- 执行器拿到引擎给的行数据,把回这个值加上1,得到一航信的数据,再调用引擎接口写入这一行数据。
- 引擎将这行数据更新到内存中,同时将这更新操作写入到redo log里面,此时redo log处于prepare状态,然后告知执行器执行完了,随时可以提交事务。
- 执行器生成这个操作的binlog,并把binlog写入到磁盘。
-
执行器调用引擎的提交事务接口,引擎把刚刚写入的redo log改成commit状态,更新完成
最后三步将redo log的写入拆成了两个步骤,prepare和commit,这就是两段提交
两阶段提交
为什么必须有两阶段提交?就是为了让两份日志之间的逻辑一直,从而可以是的数据库恢复到半月内任意一秒的状态,那么要怎样才能做到。
binlog会记录所有的binlog,那么如果要做到恢复到半个月内的任意一秒的状态,那么久意味着,保存了这半个月的binlog,同时系统会定期做整库备份
当需要恢复数据库的时候,首先要找到最近的一次全量备份。然后从备份的时间点开始,将被纷纷的Binlog依次取出来,重放到需要恢复的时刻
那么为什么需要两阶段提交?使用反证法来说明
- 先写redo log后写binlog,在binlog还没写的时候发生了crash,这个时候binlog里面没有记录这些语句。在redo log里面写完之后,后面即使发生系统崩溃,仍然能够把数据恢复过来,所以恢复之后这一行的数据是1,但是binLog里面没有记录这一次的更新语句,所以如果需要使用binlog恢复原数据库的值就是0,与原数据库值不同
- 先写binlog在写redolog,在binlog写入了更新语句,在redo log里将状态编程commit状态之前发生了crash,此时就算使用binlog将数据值恢复过来,由于在redo log里面记录的状态是repapare,那么这个事务无效,因此本次更新语句无效,恢复出来的数据值仍然是0,与原数据库值不同
因此,如果不使用两阶段提交,那么数据库的状态就有可能和用它日志恢复出来的状态不一致。
补充:
redo log记录的是数据页做了什么改动
binlog有两种模式,statement格式的话是记录sql语句,row格式会记录行的内容,记两条,更新前和更新后都有
MySQL server 层和InnoDB层都保存了表结构