今天主要简单介绍一下select语句的流程、update语句执行流程以及涉及的两阶段提交协议,仅供参考。
redo log(innodb引擎独有,循环写,空间固定会用完,比如配置一组4个文件,每个文件大小为4GB,则总共可以记录4GB的操作)是物理日志,记录的在“在某个数据页上做了什么修改”;
binlog(mysql的server层实现,日志追加写)是逻辑日志,记录的是这个语句的原始逻辑。
如果每一次数据更新的时候都需要写进磁盘,然后磁盘找对应记录,然后再更新,整个过程IO成本、查找成本都很高,而mysql数据库采用WAL(write-ahead logging)来解决这个问题,关键在先写日志,再写磁盘。也就是当数据库进行更新的时候,innodb引擎先把记录写到redo log,并更新内存,这时候更新就算完成了,同时innodb引擎会在适当时候更新到磁盘。当redo log空间不够时则先将redo log内容写到磁盘再继续工作。
1、MySQL没有开启Binary log的情况下:
InnoDB存储引擎通过redo和undo日志可以safe crash recovery数据库,当数据crash recovery时,通过redo日志将所有已经在存储引擎内部提交的事务应用redo log恢复,所有已经prepared但是没有commit的transactions将会应用undo log做roll back。然后客户端连接时就能看到已经提交的数据存在数据库内,未提交被回滚的数据需要重新执行。
2、MySQL开启Binary log 的情况下:
为了保证存储引擎和MySQL数据库上层的二进制日志保持一致(因为备库通过二进制日志重放主库提交的事务,假设主库存储引擎已经提交而二进制日志没有保持一致,则会使备库数据丢失造成主备数据不一致),引入二阶段提交(two phase commit or 2pc)
2PC协议也成为2段提交,1prepare阶段,2commit阶段。
所谓的两个阶段是指:第一阶段:准备阶段(投票阶段)和第二阶段:提交阶段(执行阶段)
为什么要有两阶段提交?这是为了让redo log和binlog日志之间的逻辑一致。
由于redo log和binlog是两个独立的逻辑,如果不用两阶段提交,那就是先写完redo log,再写binlog,或者采用反过来的顺序。
以update语句为例:update t set c = c+1 where id=2;
假设执行 update 语句过程中在写完第一个日志后,第二个日志还没有写完期间发生了 crash,会出现什么情况呢?
1、先写 redo log 后写 binlog
假设在 redo log 写完,binlog 还没有写完的时候,MySQL 进程异常重启。由于我们前面说过的,redo log 写完之后,系统即使崩溃,仍然能够把数据恢复回来,所以恢复后这一行 c 的值是 1。
但是由于 binlog 没写完就 crash 了,这时候 binlog 里面就没有记录这个语句。因此,之后备份日志的时候,存起来的 binlog 里面就没有这条语句。
然后你会发现,如果需要用这个 binlog 来恢复临时库的话,由于这个语句的 binlog 丢失,这个临时库就会少了这一次更新,恢复出来的这一行 c 的值就是 0,与原库的值不同。
即:
在写binlog之前崩溃时:
1)重启恢复:发现没有commit,回滚
2)备份恢复:没有binlog
3)结果:事务一致
2、先写 binlog 后写 redo log
如果在 binlog 写完之后 crash,由于 redo log 还没写,崩溃恢复以后这个事务无效,所以这一行 c 的值是 0。但是 binlog 里面已经记录了“把 c 从 0 改成 1”这个日志。所以,在之后用 binlog 来恢复的时候就多了一个事务出来,恢复出来的这一行 c 的值就是 1,与原库的值不同。
在redo log commit阶段之前崩溃时:
1)重启恢复:没有commit,但满足prepare和binlog完整,重启后自动commit
2)备份恢复:记录binlog
3)结果:事务一致
可以看到,如果不使用“两阶段提交”,那么数据库的状态就有可能和用它的日志恢复出来的库的状态不一致。
通过上面的内容大家可以理解mysql数据库为什么要去设计WAL,在理解select、update语句执行流程后再去理解为什么要去设计二阶段提交协议,如果没有二阶段提交协议会怎样?最后大家有空可以再去考虑三段提交协议,以及为了提高效率在mysql 5.6所设计的组提交概念。