事务的两阶段提交

一、redolog

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

1. WAL技术

write-Ahead Logging(日志先行),先写日志,再写磁盘。

当有一条记录需要更新时,InnoDB引擎会先把记录写到redo log里,并更新内存。

2. LSN

如果脏页没刷完,数据库宕机了,那么必然是需要使用 redo log 来恢复数据的。那么 redo log 应该从哪开始恢复数据呢?为解决这个问题 InnoDB 为 redo log 记录了序列号,这被称为 LSN(Log Sequence Number),可以理解为偏移量,越新的日志 LSN 越大。InnoDB 用检查点(checkpoint_lsn)指示未被刷脏页的 redo log 数据从这里开始,用 lsn 指示下一个应该被写入日志的位置。不过由于有 redo log buffer 的缘故,实际被写入磁盘的位置往往比 lsn 要小。

在这里插入图片描述

  • crash-safe:有了 redo log,InnoDB 就可以保证即使数据库发生异常重启,之前提交的记录都不会丢失,这个能力称为 crash-safe。

二、binlog

redo log 是 InnoDB 引擎特有的日志,而 Server 层也有自己的日志,称为 binlog(归档日志)。

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

为什么会有两份日志呢?

因为最开始 MySQL 里并没有 InnoDB 引擎。MySQL 自带的引擎是 MyISAM,但是 MyISAM 没有 crash-safe 的能力,binlog 日志只能用于归档。而 InnoDB 是另一个公司以插件形式引入 MySQL 的,既然只依靠 binlog 是没有 crash-safe 能力的,所以 InnoDB 使用另外一套日志系统——也就是 redo log 来实现 crash-safe 能力。

1. binlog&redolog

  1. redolog是innodb引擎特有的;binlog是MySQL的Server层实现的,所有引擎都可以使用。
  2. redolog是物理日志,记录的是”在某个数据页上做了什么修改“;binlog是逻辑日志,记录的是这个语句的原始逻辑。比如”给ID=2这一行的c字段加1“。
  3. redolog是循环写的,空间固定会用完;binlog是可以追加写入的。”追加写“是指binlog文件写到一定大小后会切换到下一个,并不会覆盖以前的日志。

三、事务的两阶段提交

https://zhuanlan.zhihu.com/p/552706911?utm_medium=referral

1. 为什么

为什么必须有“两阶段提交”呢?

这是为了让两份日志之间的逻辑一致。

由于 redo log 和 binlog 是两个独立的逻辑,如果不用两阶段提交,要么就是先写完 redo log 再写 binlog,或者采用反过来的顺序。

  1. 有 binlog 为什么还要 redo log ?
    1. binlog 不知道数据库究竟是在哪一时刻丢失了哪部分数据,只能从备份点开始对 binlog 记录重放来恢复数据,比较耗时。
    2. binlog 恢复是需要我们手动执行的,而 redo log 可以在服务器重启后自动恢复数据。
    3. WAL + 先写缓冲 + 异步刷脏页有效提升了磁盘的 IO 效率。
  2. 有 redo log 为什么还要 binlog?
    1. binlog 是服务器层面的功能,redo log 是 innoDB 的功能。redo log 帮助 InnoDB 实现了性能提升、自动恢复。但其他存储引擎是无法使用 redo log 的能力的。
    2. 我们也可以关闭 binlog,但大多数情况下我们都会开启,因为开启的好处更多。比如,主从模式需要订阅 binlog 进行主从复制,以及可以通过 binlog 进行数据库的增量备份和恢复。

2. 场景

某天14:00发现中午12:00有一次误删表,需要找回数据

  • 首先,找到最近的一次全量备份,如果你运气好,可能就是昨天晚上的一个备份,从这个备份恢复到临时库;
  • 然后,从备份的时间点开始,将备份的 binlog 依次取出来,重放到中午误删表之前的那个时刻。
  • 把表数据从临时库取出来,按需要恢复到线上库去。

3. 流程

  1. 服务器收到事务开始的指令,为事务生成一个全局唯一的事务 id。这个事务 id 在记录 binlog 和 redo log 时都会使用。
  2. 如果缓存池中没有 no=1 所在数据页的数据,从磁盘中找到对应的数据页(注意,这里是一个数据页,不是一条记录),把数据页加载到缓存。
  3. 修改缓存数据页中 no=1 的数据。
  4. 记录数据到 redo log buffer[^4]、binlog cache[^2]。根据 redo log 刷盘的策略,这个过程中 redo log buffer 可能会被刷新到磁盘。
  5. 服务器收到事务提交的指令。
  6. 刷新 redo log buffer 到磁盘,并标记该事务的状态为 prepare。此操作称为 redo log prepare。
  7. 刷新 binlog cache 到磁盘。
  8. 刷新 redo log buffer 到磁盘,并标记该事务的状态为 commit。此操作称为 redo log commit。
  9. 向客户端返回事务执行的结果。

在宕机后,重启 MySQL 时,InnoDB 会自动恢复 redo log 中 checkpoint_lsn 后的,且处于 commit 状态的事务。如果 redo log 中事务的状态为 prepare,则需要先查看 binlog 中该事务是否存在,是的话就恢复,否则就回滚(通过 undo log 回滚。脏页一直在刷,更新了脏页,但事务没提交就宕机了,所以需要回滚)。

4. 异常处理

组提交_并行复制_数据库_02

如果在MYSQL重启之后会按照顺序读取redo log 文件,当碰到处于 prepare阶段的redo log 文件,就会拿着redo log 的XID去binlog中寻找,看是否有该XID:

  1. 如果binlog中没有该XID,那么就说明redo log 刷盘完成,但binlog还没有,则回滚事务,即时刻A阶段异常

  2. 如果binlog中有该XID,那么就说明redo log 和binlog都刷盘完成,但commit标识还没有提交,则提交事务,即时刻B阶段异常

所以从上面可以看出,对于处于 prepare 阶段的 redo log,即可以提交事务,也可以回滚事务,这取决于是否能在 binlog 中查找到与 redo log 相同的 XID,如果有就提交事务,如果没有就回滚事务。这样就可以保证 redo log 和 binlog 这两份日志的一致性了。

总结:两阶段提交是以 binlog 写成功为事务提交成功的标识,因为 binlog 写成功了,就意味着能在 binlog 中查找到与 redo log 相同的 XID。

问题1:如果事务还没有提交,redo log会持久化吗?

答案:会持久化。因为事务执行过程中redo log 会直接写到 redo log buffer中,这些在 redo log buffer里的redo log 会每隔一秒就被持久化到磁盘中(根据持久化策略决定)。所以事务没有提交,redo log可能会持久化到磁盘的。

问题2: 如果在事务还没有提交,而redo log 已经被持久化磁盘了。这时MYSQL崩溃了,怎么办?

答案:在MYSQL重启时,事务会进行回滚操作。因为事务没提交的时候,binlog 是还没持久化到磁盘的。

5. 性能影响

两阶段提交解决两个日志一致性,那它会不会带来新的问题呢?
虽然两阶段提交是解决了两个日志数据一致性问题,但是它也带来了一定性能问题:

  • 磁盘 I/O 次数高:对于“双1”配置(sync_binlog 和 innodb_flush_log_at_trx_commit 都配置为 1),每个事务提交都会进行两次 fsync(刷盘),一次是 redo log 刷盘,另一次是 binlog 刷盘

  • 锁竞争激烈:两阶段提交虽然能够保证「单事务」两个日志的内容一致,但在「多事务」的情况下,却不能保证两者的提交顺序一致,因此,在两阶段提交的流程基础上,还需要加一个锁来保证提交的原子性,从而保证多事务的情况下,两个日志的提交顺序一致。

磁盘 I/O 次数高

因为redo log 和binlog 都是存在对应的缓存里,即redo log缓存在redo log buffer,binlog缓存在 binlog cache中,而持久化它们是各自通过参数来控制的。一般为了数据不会丢失,都会设置这两个参数为1:

  • 当 sync_binlog = 1 的时候,表示每次提交事务都会将 binlog cache 里的 binlog 直接持久到磁

  • 当 innodb_flush_log_at_trx_commit = 1 时,表示每次事务提交时,都将缓存在 redo log buffer 里的 redo log 直接持久化到磁盘

  • 这两个参数都设置为1,就是“双1”配置。那么当有事务提交的时候,至少就是刷两个磁盘(分别是 redo log 刷盘和binlog 刷盘),所以就导致了性能问题。

  • 21
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
分布式事务阶段提交(Two-Phase Commit,2PC)是一种常见的分布式事务协议,用于协调多个参与者节点的提交或回滚操作。它包括以下阶段: 1. 准备阶段(Prepare Phase):在准备阶段,协调者节点(也称为事务管理器)向所有参与者节点发送准备请求,并等待它们的响应。参与者节点接收到准备请求后,会执行事务的准备操作,检查是否能够成功执行事务。如果参与者节点准备就绪,它会向协调者节点发送“同意”响应,表示可以进行提交操作。如果有任何一个参与者节点无法准备就绪,它会向协调者节点发送“中止”响应,表示无法执行事务。 2. 提交阶段(Commit Phase):在提交阶段,如果所有参与者节点都发送了“同意”响应,协调者节点会向所有参与者节点发送提交请求,要求它们执行事务提交操作。参与者节点接收到提交请求后,会执行事务提交操作,并向协调者节点发送“完成”响应。如果有任何一个参与者节点无法执行提交操作,它会向协调者节点发送“中止”响应。 协调者节点在等待一定时间后,收集所有参与者节点的响应,如果收到了所有参与者节点的“完成”响应,它会向所有参与者节点发送“全局提交”指令,表示事务提交完成。如果协调者节点收到了任何一个参与者节点的“中止”响应或超时,它会向所有参与者节点发送“全局回滚”指令,表示事务的回滚。 阶段提交协议保证了分布式事务的原子性和一致性,但它也存在一些缺点,比如协调者节点的单点故障、阻塞等待参与者节点响应导致性能问题等。因此,在实际应用中,还需要考虑其他的分布式事务解决方案来满足具体的业务需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码精灵

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值