什么是事务
事务是数据库中一组原子性的操作,要么全部成功,要么全部失败。事务具有四个特性,即原子性(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 的状态来判断事务是否提交,并保证数据的一致性。
两阶段提交的流程
下面我们通过一个例子来具体看看两阶段提交的流程。假设我们有一个事务 T,它执行了以下 SQL 语句:
begin;
update account set balance = balance - 100 where id = 1;
update account set balance = balance + 100 where id = 2;
commit;
这个事务的两阶段提交的流程如下:
-
在 prepare 阶段,事务 T 会先将两条 update 语句的修改记录到 redo log buffer 中,然后将 redo log buffer 刷新到 redo log file 中,并将其标记为 prepare 状态。此时,redo log file 中会有类似以下的内容:
LSN | XID | TYPE | DATA ----|-----|------|----- 100 | T | MLOG_UNDO_HDR | ... 101 | T | MLOG_UNDO_INSERT | ... 102 | T | MLOG_UNDO_INSERT | ... 103 | T | MLOG_2PC_PREPARE | ...
其中,LSN 是日志序列号,XID 是事务 ID,TYPE 是日志类型,DATA 是日志内容。MLOG_UNDO_HDR 表示 undo 日志的头部信息,MLOG_UNDO_INSERT 表示 undo 日志的插入操作,MLOG_2PC_PREPARE 表示两阶段提交的 prepare 操作。
-
然后,事务 T 会将两条 update 语句记录到 binlog 中,并将 binlog 写入到 binlog 文件中。此时,binlog 文件中会有类似以下的内容:
LOG_POS | XID | TYPE | DATA --------|-----|------|----- 200 | T | QUERY_EVENT | begin 201 | T | QUERY_EVENT | update account set balance = balance - 100 where id = 1 202 | T | QUERY_EVENT | update account set balance = balance + 100 where id = 2
其中,LOG_POS 是日志位置,XID 是事务 ID,TYPE 是日志类型,DATA 是日志内容。QUERY_EVENT 表示 SQL 语句的事件。
-
在 commit 阶段,事务 T 会将 redo log 的状态从 prepare 改为 commit,并将 binlog 落盘。此时,redo log file 中会有类似以下的内容:
LSN | XID | TYPE | DATA ----|-----|------|----- 100 | T | MLOG_UNDO_HDR | ... 101 | T | MLOG_UNDO_INSERT | ... 102 | T | MLOG_UNDO_INSERT | ... 103 | T | MLOG_2PC_PREPARE | ... 104 | T | MLOG_2PC_COMMIT |
-
最后,事务 T 完成提交,并释放锁资源。
总结
MySQL 的两阶段提交是为了保证同时使用 redo log 和 binlog 的情况下,数据的一致性。两阶段提交将一个事务分成 prepare 阶段和 commit 阶段,在 prepare 阶段写 redo log 和 binlog,在 commit 阶段修改 redo log 的状态并落盘 binlog。这样可以避免数据库发生异常重启或者主从切换时出现数据不一致的情况。
标签:binlog,事务,log,MLOG,提交,MySQL,日志,redo From: https://www.cnblogs.com/shoshana-kong/p/17471621.html