1 两阶段提交
以update语句的具体执行过程为例:
具体更新一条记录 UPDATE t_user SET name = ‘xiaolin’ WHERE id = 1;的流程如下:
1.执行器负责具体执行,会调用存储引擎的接口,通过主键索引树搜索获取 id = 1 这一行记录:
(1)如果 id=1 这一行所在的数据页本来就在 buffer pool 中,就直接返回给执行器更新;
(2)如果记录不在 buffer pool,将数据页从磁盘读入到 buffer pool,返回记录给执行器。
2.执行器得到聚簇索引记录后,会看一下更新前的记录和更新后的记录是否一样:
(1)如果一样的话就不进行后续更新流程;
(2)如果不一样的话就把更新前的记录和更新后的记录都当作参数传给 InnoDB 层,让 InnoDB 真正的执行更新记录的操作;
3.开启事务, InnoDB 层更新记录前,首先要记录相应的 undo log,因为这是更新操作,需要把被更新的列的旧值记下来,也就是要生成一条 undo log,并被写入undo log buffer
里
4.InnoDB 层开始更新记录,会先更新buffer pool里相关的页(并标记为脏页)。刷新到磁盘,有相应的机制,刷新策略控制参数innodb_max_dirty_pages_pct,innodb_max_dirty_pages_pct_lwm
5.生成 redo log,写入redo log buffer
里,此时到了redo log两阶段提交的第一阶段 preparing阶段
。刷新到磁盘,有相应的机制,由参数innodb_autoinc_lock_mode控制。
6.引擎层 在一条更新语句执行完成后,server层开始记录该语句对应的 binlog,此时记录的 binlog 会被保存到binlog cache。刷新到磁盘,有相应的机制,刷新策略控制参数sync_binlog;只要 bin log 写磁盘成功,就算 redo log 的状态还是 prepare 也没有关系,一样会被认为事务已经执行成功;binlog是二进制日志,有三种类型,由参数binlog_format【Mysql–基础知识点–88–各种mysql日志中的2】控制
7.此时到了redo log两阶段提交的第二阶段commit 阶段
8.至此,一条更新语句执行完成。
2 为什么要两阶段提交呢?直接提交不行吗?
我们可以假设不采用两阶段提交的方式,而是采用"单阶段"进行提交,即要么先写入redo log,后写入binlog;要么先写入binlog,后写入redo log。这两种方式的提交都会导致原先数据库的状态和被恢复后的数据库的状态不一致。
先写入 redo log,后写入 binlog:
在写完redo log之后,数据此时具有crash-safe能力,因此系统崩溃,数据会恢复成事务开始之前的状态。但是,若在redo log写完时候,binlog写入之前,系统发生了宕机。此时binlog没有对上面的更新语句进行保存,导致当使用binlog进行数据库的备份或者恢复时,就少了上述的更新语句。从而使得id=2这一行的数据没有被更新。
先写入 binlog,后写入 redo log:
写完 binlog 之后,所有的语句都被保存,所以通过 binlog 复制或恢复出来的数据库中 id=2 这一行的数据会被更新为a=1。但是如果在redo log写入之前,系统崩溃,那么redo log中记录的这个事务会无效,导致实际数据库中 id=2 这一行的数据并没有更新。
简单说,redo log和binlog都可以用于表示事务的提交状态,而两阶段提交就是让这两个状态保持逻辑上的一致。
3 崩溃恢复时的处理
若MySQL在提交过程中崩溃,重启后会检查redo log和binlog的一致性:
Case 1:redo log为prepare且binlog完整
说明binlog已写入,但redo log未提交。
恢复操作:提交事务(将数据修改生效)。
Case 2:redo log为prepare但binlog不完整
说明第二阶段未完成。
恢复操作:回滚事务(利用undo log撤销修改)。
通过这种机制,确保redo log和binlog的数据严格一致。