lotus

贵有恒何必三更眠五更起 最无益只怕一日曝十日寒

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

 

什么是事务

事务是数据库中一组原子性的操作,要么全部成功,要么全部失败。事务具有四个特性,即原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability),简称 ACID。

在 MySQL 中,我们可以使用 begin 或 start transaction 命令开启一个事务,使用 commit 命令提交一个事务,或者使用 rollback 命令回滚一个事务。例如:

begin;
update account set balance = balance - 100 where id = 1;
update account set balance = balance + 100 where id = 2;
commit;

上面的例子是一个转账的场景,我们将 id 为 1 的账户的余额减少 100,将 id 为 2 的账户的余额增加 100。这两个操作要么同时成功,要么同时失败,不能出现中间状态。

MySQL 中的日志

为了保证事务的 ACID 特性,MySQL 需要记录一些日志来辅助事务的执行和恢复。MySQL 中主要有两种日志:redo log 和 binlog。

redo log 是 InnoDB 存储引擎特有的日志,用于记录数据页的物理修改,保证事务的持久性和原子性。redo log 是循环写入的,由两部分组成:一块固定大小的内存区域(redo log buffer)和一组固定大小的磁盘文件(redo log file)。当事务对数据进行修改时,会先将修改记录到 redo log buffer 中,然后在适当的时机将其刷新到 redo log file 中。这样即使数据库发生异常重启,也可以根据 redo log 恢复数据。

binlog 是 MySQL Server 层的日志,用于记录 SQL 语句的逻辑修改,保证事务的一致性。binlog 是追加写入的,由一个 binlog 文件序列和一个索引文件组成。当事务提交时,会将 SQL 语句记录到 binlog 中。binlog 主要用于数据备份、恢复和主从复制。

为什么需要两阶段提交

如果只有 redo log 或者只有 binlog,那么事务就不需要两阶段提交。但是如果同时使用了 redo log 和 binlog,那么就需要保证这两种日志之间的一致性。否则,在数据库发生异常重启或者主从切换时,可能会出现数据不一致的情况。

例如,假设我们有一个事务 T,它修改了两行数据 A 和 B,并且同时开启了 redo log 和 binlog。如果我们先写 redo log 再写 binlog,并且在写完 redo log 后数据库发生了宕机,那么在重启后,根据 redo log 我们可以恢复 A 和 B 的修改,但是 binlog 中没有记录 T 的信息,导致备份或者从库中没有 T 的修改。反之,如果我们先写 binlog 再写 redo log,并且在写完 binlog 后数据库发生了宕机,那么在重启后,根据 redo log 我们无法恢复 A 和 B 的修改,但是 binlog 中有记录 T 的信息,导致备份或者从库中有 T 的修改。

为了避免这种情况,MySQL 引入了两阶段提交的机制。两阶段提交就是将一个事务分成两个阶段来提交:prepare 阶段和 commit 阶段。在 prepare 阶段,事务会先写 redo log 并将其标记为 prepare 状态,然后写 binlog;在 commit 阶段,事务会将 redo log 标记为 commit 状态,并将 binlog 落盘。这样,无论数据库在哪个时刻发生宕机,都可以根据 redo log 和 binlog 的状态来判断事务是否提交,并保证数据的一致性。

两阶段提交的流程

 

MySQL采用了如下的二阶段提交流程:

  1. 在准备阶段,MySQL先将数据修改写入redo log,并将其标记为prepare状态,表示事务还未提交。然后将对应的SQL语句写入bin log。
  2. 在提交阶段,MySQL将redo log标记为commit状态,表示事务已经提交。然后根据sync_binlog参数的设置,决定是否将bin log刷入磁盘。

 

通过这样的流程,MySQL可以保证在任何时刻,redo log和bin log都是逻辑上一致的。如果MySQL发生崩溃,可以根据redo log恢复数据页的状态,也可以根据bin log恢复SQL语句的执行。

 

 

下面是一个简单的示例:

假设我们有一个表test_backup如下:

CREATE TABLE `test_backup` (
  `id` int (11) NOT NULL AUTO_INCREMENT,
  `name` varchar (255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

然后我们执行以下语句:

insert into test_backup values (1,'tom');
insert into test_backup values (2,'jerry');
insert into test_backup values (1,'herry');

这时候,MySQL会按照以下步骤进行二阶段提交:

  1. 将第一条插入语句写入redo log,并标记为prepare状态。
  2. 将第一条插入语句写入bin log。
  3. 将redo log标记为commit状态。
  4. 如果sync_binlog=1,则将bin log刷入磁盘。
  5. 重复以上步骤,直到所有插入语句都完成。

如果在这个过程中发生了崩溃,比如在第三步之前,那么MySQL重启后会根据redo log发现有一个prepare状态的事务,然后会去查找bin log中是否有对应的SQL语句。如果有,则说明该事务已经写入了bin log,可以提交;如果没有,则说明该事务还没有写入bin log,需要回滚。这样就可以保证数据的一致性。

总结

MySQL 的两阶段提交是为了保证同时使用 redo log 和 binlog 的情况下,数据的一致性。两阶段提交将一个事务分成 prepare 阶段和 commit 阶段,在 prepare 阶段写 redo log 和 binlog,在 commit 阶段修改 redo log 的状态并落盘 binlog。这样可以避免数据库发生异常重启或者主从切换时出现数据不一致的情况。

posted on 2023-06-10 17:25  白露~  阅读(7305)  评论(0编辑  收藏  举报