分布式事务
- 1.分布式事务是什么?
- 数据库事务
- 2.分布式事务产生的原因?
- 存储层拆分
- 服务层拆分
- 3.分布式事务解决方案
- 4.分布式事务有哪些开源组件
- Seate
- TCC 分布式服务组件
- 基于消息补偿的最终一致性
- 5.两阶段提交,三阶段协议详解
- 二阶段提交协议
- 三阶段提交协议
- 三阶段提交的改进
- 两阶段和三阶段提交的应用
- 6.TCC 事务模型详解
- TCC 事务模型是什么?
- TCC 的各个阶段
- TCC 的优缺点
- 真实业务场景 TCC
- 7.MySQL 数据库如何实现 XA (内部分布式事务)规范
- MySQL 有哪些一致性日志
- XA 规范是如何定义的
- XA 事务的执行流程
- MySQL 如何实现 XA 规范
- Binlog 同步过程
1.分布式事务是什么?
分布式事务关注的是分布式场景下如何处理事务,是指事务的参与者、支持事务操作的服务器、存储等资源分别位于分布式系统的不同节点之上。
分布式事务就是一个业务操作,是由多个细分操作完成的,而这些细分操作又分布在不同的服务器上;
事务,就是这些操作要么全部成功执行,要么全部不执行。
数据库事务
数据库事务的特性包括原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durabilily),简称 ACID。
在数据库执行中,多个并发执行的事务如果涉及到同一份数据的读写就容易出现数据不一致的情况,不一致的异常现象有以下几种。
-
脏读,是指一个事务中访问到了另外一个事务未提交的数据。例如事务 T1 中修改的数据项在尚未提交的情况下被其他事务(T2)读取到,如果 T1 进行回滚操作,则 T2 刚刚读取到的数据实际并不存在。
-
**不可重复读,是指一个事务读取同一条记录 2 次,得到的结果不一致。**例如事务 T1 第一次读取数据,接下来 T2 对其中的数据进行了更新或者删除,并且 Commit 成功。这时候 T1 再次读取这些数据,那么会得到 T2 修改后的数据,发现数据已经变更,这样 T1 在一个事务中的两次读取,返回的结果集会不一致。
-
幻读,是指一个事务读取 2 次,得到的记录条数不一致。例如事务 T1 查询获得一个结果集,T2 插入新的数据,T2 Commit 成功后,T1 再次执行同样的查询,此时得到的结果集记录数不同。
-
脏读、不可重复读和幻读有以下的包含关系,如果发生了脏读,那么幻读和不可重复读都有可能出现。
不同隔离级别
SQL 标准根据三种不一致的异常现象,将隔离性定义为四个隔离级别(Isolation Level),隔离级别和数据库的性能呈反比,隔离级别越低,数据库性能越高;而隔离级别越高,数据库性能越差,具体如下:
(1)Read uncommitted 读未提交
在该级别下,一个事务对数据修改的过程中,不允许另一个事务对该行数据进行修改,但允许另一个事务对该行数据进行读,不会出现更新丢失,但会出现脏读、不可重复读的情况。
(2)Read committed 读已提交
在该级别下,未提交的写事务不允许其他事务访问该行,不会出现脏读,但是读取数据的事务允许其他事务访问该行数据,因此会出现不可重复读的情况。
(3)Repeatable read 可重复读
在该级别下,在同一个事务内的查询都是和事务开始时刻一致的,保证对同一字段的多次读取结果都相同,除非数据是被本身事务自己所修改,不会出现同一事务读到两次不同数据的情况。因为没有约束其他事务的新增Insert操作,所以 SQL 标准中可重复读级别会出现幻读。
可重复读是 MySQL InnoDB 引擎的默认隔离级别,但是在 MySQL 额外添加了间隙锁(Gap Lock),可以防止幻读。
(4)Serializable 序列化
该级别要求所有事务都必须串行执行,可以避免各种并发引起的问题,效率也最低。
对不同隔离级别的解释,其实是为了保持数据库事务中的隔离性(Isolation),目标是使并发事务的执行效果与串行一致,隔离级别的提升带来的是并发能力的下降,两者是负相关的关系。
2.分布式事务产生的原因?
分布式事务是伴随着系统拆分出现的,前面我们说过,分布式系统解决了海量数据服务对扩展性的要求,但是增加了架构上的复杂性,在这一点上,分布式事务就是典型的体现。
**在实际开发中,**分布式事务产生的原因主要来源于存储和服务的拆分。
存储层拆分
存储层拆分,最典型的就是数据库分库分表,当单表容量达到千万级,就要考虑数据库拆分,从单一数据库变成多个分库和多个分表。在业务中如果需要进行跨库或者跨表更新,同时要保证数据的一致性,就产生了分布式事务问题。
服务层拆分
服务层拆分也就是业务的服务化,系统架构的演进是从集中式到分布式,业务功能之间越来越解耦合。
比如电商网站系统,业务初期可能是一个单体工程支撑整套服务,但随着系统规模进一步变大,大多数公司都会将核心业务抽取出来,以作为独立的服务。商品、订单、库存、账号信息都提供了各自领域的服务,业务逻辑的执行散落在不同的服务器上。
用户如果在某网站上进行一个下单操作,那么会同时依赖订单服务、库存服务、支付扣款服务,这几个操作如果有一个失败,那下单操作也就完不成,这就需要分布式事务来保证了。
3.分布式事务解决方案
分布式事务的解决方案,典型的有两阶段和三阶段提交协议、 TCC 分段提交,和基于消息队列的最终一致性设计。
2PC 两阶段提交
两阶段提交(2PC,Two-phase Commit Protocol)是非常经典的强一致性、中心化的原子提交协议,在各种事务和一致性的解决方案中,都能看到两阶段提交的应用。
3PC 三阶段提交
三阶段提交协议(3PC,Three-phase_commit_protocol)是在 2PC 之上扩展的提交协议,主要是为了解决两阶段提交协议的阻塞问题,从原来的两个阶段扩展为三个阶段,增加了超时机制。
TCC 分段提交
TCC 是一个分布式事务的处理模型,将事务过程拆分为 Try尝试、Confirm确定、Cancel 取消三个步骤,在保证强一致性的同时,最大限度提高系统的可伸缩性与可用性。
基于消息补偿的最终一致性(√)
异步化在分布式系统设计中随处可见,基于消息队列的最终一致性就是一种异步事务机制,在业务中广泛应用。
4.分布式事务有哪些开源组件
Seate
分布式事务开源组件应用比较广泛的是蚂蚁金服开源的 Seata,也就是 Fescar,前身是阿里中间件团队发布的 TXC(Taobao Transaction Constructor)和升级后的 GTS(Global Transaction Service)。
Seata 的设计思想是把一个分布式事务拆分成一个包含了若干分支事务(Branch Transaction)的全局事务(Global Transaction)。分支事务本身就是一个满足 ACID 的 本地事务,全局事务的职责是协调其下管辖的分支事务达成一致,要么一起成功提交,要么一起失败回滚。
在Seata 中,全局事务对分支事务的协调基于两阶段提交协议,类似数据库中的 XA 规范,XA 规范定义了三个组件来协调分布式事务,分别是 AP 应用程序、TM 事务管理器、RM 资源管理器、CRM 通信资源管理器。
TCC 分布式服务组件
在业务中引入 TCC 一般是依赖单独的 TCC 事务框架,可以选择自研或者应用开源组件。TCC 框架扮演了资源管理器的角色,常用的 TCC 开源组件有 Tcc-transaction、ByteTCC、Spring-cloud-rest-tcc 等。
TCC 事务模式,也支持了 AT 模式及 Saga 模式。
以 Tcc-transaction https://github.com/changmingxie/tcc-transaction为例,源码托管在 Github-tcc-transaction,提供了对 Spring 和 Dubbo 的适配, tcc-transaction-tutorial-samplehttps://github.com/changmingxie/tcc-transaction/tree/master-1.2.x/tcc-transaction-tutorial-sample 学习。
基于消息补偿的最终一致性
在具体实现上,基于消息补偿的一致性主要有本地消息表和第三方可靠消息队列等。
本地消息表,本地消息表的方案核心思想是将分布式事务拆分成本地事务进行处理,通过消息日志的方式来异步执行。
本地消息表是一种业务耦合的设计,消息生产方需要额外建一个事务消息表,并记录消息发送状态,消息消费方需要处理这个消息,并完成自己的业务逻辑,另外会有一个异步机制来定期扫描未完成的消息,确保最终一致性。
下单减库存业务来简单模拟本地消息表的实现过程:
(1)系统收到下单请求,将订单业务数据存入到订单库中,并且同时存储该订单对应的消息数据,比如购买商品的 ID 和数量,消息数据与订单库为同一库,更新订单和存储消息为一个本地事务,要么都成功,要么都失败。
(2)库存服务通过消息中间件收到库存更新消息,调用库存服务进行业务操作,同时返回业务处理结果。
(3)消息生产方,也就是订单服务收到处理结果后,将本地消息表的数据删除或者设置为已完成。
(4)设置异步任务,定时去扫描本地消息表,发现有未完成的任务则重试,保证最终一致性。
在具体实践中,还有许多分支情况,比如消息发送失败、下游业务方处理失败等
5.两阶段提交,三阶段协议详解
二阶段提交协议
二阶段提交算法的成立:
在该分布式系统中,存在一个节点作为 协调者(Coordinator),其他节点作为 参与者(Participants),且节点之间可以进行网络通信;
所有节点都采用预写式日志,日志被写入后被保存在可靠的存储设备上,即使节点损坏也不会导致日志数据的丢失;
所有节点不会永久性损坏,即使损坏后仍然可以恢复。
两阶段提交中的两个阶段,指的是 Commit-request 阶段和 Commit 阶段,两阶段提交的流程如下:
提交请求阶段
在提交请求阶段,协调者将通知 事务参与者准备提交事务,然后进入表决过程。在表决过程中,参与者将告知协调者自己的决策:同意(事务参与者本地事务执行成功)或取消(本地事务执行故障),在第一阶段,参与节点并没有进行Commit操作。
提交阶段
在提交阶段,协调者将基于第一个阶段的投票结果进行决策:提交或取消这个事务。这个结果的处理必须当且仅当所有的参与者同意提交,协调者才会通知各个参与者提交事务,否则协调者将通知各个参与者取消事务。
参与者在接收到协调者发来的消息后将执行对应的操作,也就是本地 Commit 或者 Rollback。
两阶段提交存在的问题
两阶段提交协议有几个明显的问题:
资源被同步阻塞
- 在执行过程中,所有参与节点都是事务独占状态,当参与者占有公共资源时,那么第三方节点访问公共资源会被阻塞。
协调者可能出现单点故障
- 一旦协调者发生故障,参与者会一直阻塞下去。
在 Commit 阶段出现数据不一致
- 在第二阶段中,假设协调者发出了事务 Commit 的通知,但是由于网络问题该通知仅被一部分参与者所收到并执行 Commit,其余的参与者没有收到通知,一直处于阻塞状态,那么,这段时间就产生了数据的不一致性。
三阶段提交协议
为了解决二阶段协议中的同步阻塞等问题,三阶段提交协议在协调者和参与者中都引入了【超时机制】,并且把两阶段提交协议的第一个阶段拆分成了两步:询问,然后再锁资源,最后真正提交。
三阶段中的 Three Phase 分别为 CanCommit、PreCommit、DoCommit 阶段。
CanCommit 阶段(准备阶段):
3PC 的 CanCommit 阶段其实和 2PC 的准备阶段很像。协调者向参与者发送 Can-Commit 请求,参与者如果可以提交就返回 Yes 响应,否则返回 No 响应。
PreCommit 阶段(预提交阶段):
协调者根据参与者的反应情况来决定是否可以继续事务的 PreCommit 操作。根据响应情况,有以下两种可能。
A. 假如协调者从所有的参与者获得的反馈都是 Yes 响应,那么就会进行事务的预执行:
-
发送预提交请求,协调者向参与者发送 PreCommit 请求,并进入 Prepared 阶段;
-
事务预提交,参与者接收到 PreCommit 请求后,会执行事务操作;
-
响应反馈,如果参与者成功执行了事务操作,则返回 ACK 响应,同时开始等待最终指令。
B. 假如有任何一个参与者向协调者发送了 No 响应,或者等待超时之后,协调者都没有接到参与者的响应,那么就中断事务:
-
发送中断请求,协调者向所有参与者发送 abort 请求;
-
中断事务,参与者收到来自协调者的 abort 请求之后,执行事务的中断。
DoCommit 阶段(提交阶段):
该阶段进行真正的事务提交,也可以分为以下两种情况。
A. 执行提交
- 发送提交请求。协调者接收到参与者发送的 ACK 响应后,那么它将从预提交状态进入到提交状态,并向所有参与者发送 doCommit 请求。
- 事务提交。参与者接收到 doCommit 请求之后,执行正式的事务提交,并在完成事务提交之后释放所有事务资源。
- 响应反馈。事务提交完之后,向协调者发送 ACK 响应。
- 完成事务。协调者接收到所有参与者的 ACK 响应之后,完成事务。
B. 中断事务
- 协调者没有接收到参与者发送的 ACK 响应,可能是因为接受者发送的不是 ACK 响应,也有可能响应超时了,那么就会执行中断事务。
C.超时提交
- 参与者如果没有收到协调者的通知,超时之后会执行 Commit 操作。
三阶段提交的改进
1.引入超时机制
在 2PC 中,只有协调者拥有超时机制,如果在一定时间内没有收到参与者的消息则默认失败,3PC 同时在协调者和参与者中都引入超时机制。
2.添加预提交阶段
在 2PC 的准备阶段和提交阶段之间,插入一个准备阶段,使 3PC 拥有 CanCommit、PreCommit、DoCommit 三个阶段,PreCommit 是一个缓冲,保证了在最后提交阶段之前各参与节点的状态是一致的。
3.三阶段提交协议存在的问题
三阶段提交协议同样存在问题,具体表现为,在阶段三中,如果参与者接收到了 PreCommit 消息后,出现了不能与协调者正常通信的问题,在这种情况下,参与者依然会进行事务的提交,这就出现了数据的不一致性。
两阶段和三阶段提交的应用
-
两阶段提交是一种比较精简的一致性算法/协议,很多关系型数据库都是采用两阶段提交协议来完成分布式事务处理的,典型的比如 MySQL 的 XA 规范。
-
在事务处理、数据库和计算机网络中,两阶段提交协议提供了分布式设计中的数据一致性的保障,整个事务的参与者要么一致性全部提交成功,要么全部回滚。MySQL Cluster 内部数据的同步就是用的 2PC 协议。
6.TCC 事务模型详解
TCC 事务模型是什么?
TCC(Try-Confirm-Cancel)的概念来源于 Pat Helland 发表的一篇名为“Life beyond Distributed Transactions:an Apostate’s Opinion”的论文。
TCC 提出了一种新的事务模型,基于业务层面的事务定义,锁粒度完全由业务自己控制,目的是解决复杂业务中,跨表跨库等大颗粒度资源锁定的问题。
TCC 把事务运行过程分成 Try、Confirm / Cancel (尝试、确认与取消)两个阶段,每个阶段的逻辑由业务代码控制,避免了长事务,可以获取更高的性能。
TCC 的各个阶段
流程图:
-
Try 阶段:调用 Try 接口,尝试执行业务,完成所有业务检查,预留业务资源。
-
Confirm 或 Cancel 阶段:两者是互斥的,只能进入其中一个,并且都满足幂等性,允许失败重试。
-
Confirm 操作:对业务系统做确认提交,确认执行业务操作,不做其他业务检查,只使用 Try 阶段预留的业务资源。
-
Cancel 操作:在业务执行错误,需要回滚的状态下执行业务取消,释放预留资源。
-
Try 阶段失败可以 Cancel,如果 Confirm 和 Cancel 阶段失败了怎么办?
- TCC 中会添加事务日志,如果 Confirm 或者 Cancel 阶段出错,则会进行重试,所以这两个阶段需要支持幂等。
- 如果重试失败,则需要人工介入进行恢复和处理等。
TCC 的优缺点
实际开发中,TCC 的本质是把数据库的二阶段提交上升到微服务来实现,从而避免数据库二阶段中长事务引起的低性能风险。
TCC 解决了跨服务的业务操作原子性问题,比如下订单减库存,多渠道组合支付等场景,通过 TCC 对业务进行拆解,可以让应用自己定义数据库操作的粒度,可以降低锁冲突,提高系统的业务吞吐量。
TCC 的不足主要体现在对微服务的侵入性强,TCC 需要对业务系统进行改造,业务逻辑的每个分支都需要实现 try、Confirm、Cancel 三个操作,并且 Confirm、Cancel 必须保证幂等。
另外 TCC 的事务管理器要记录事务日志,也会损耗一定的性能。
真实业务场景 TCC
以一个电商中的支付业务来演示,用户在支付以后,需要进行更新订单状态、扣减账户余额、增加账户积分和扣减商品操作。
在实际业务中为了防止超卖,有下单减库存和付款减库存的区别,支付除了账户余额,还有各种第三方支付等,这里我们为了描述方便,统一使用扣款减库存,扣款来源是用户账户余额。
我们把订单业务拆解为以下几个步骤:
- 订单更新为支付完成状态
- 扣减用户账户余额
- 增加用户账户积分
- 扣减当前商品的库存
如果不使用事务,上面的几个步骤都可能出现失败,最终会造成大量的数据不一致,比如订单状态更新失败,扣款却成功了;或者扣款失败,库存却扣减了等情况,这个在业务上是不能接受的,会出现大量的客诉。
如果直接应用事务,不使用分布式事务,比如在代码中添加 Spring 的声明式事务 @Transactional 注解,这样做实际上是在事务中嵌套了远程服务调用,一旦服务调用出现超时,事务无法提交,就会导致数据库连接被占用,出现大量的阻塞和失败,会导致服务宕机。另一方面,如果没有定义额外的回滚操作,比如遇到异常,非 DB 的服务调用失败时,则无法正确执行回滚。
所以需要对业务代码改造,抽象 Try、Confirm 和 Cancel 阶段。
Try 操作
Try 操作一般都是锁定某个资源,设置一个预备的状态,冻结部分数据。
比如,订单服务添加一个预备状态,修改为 UPDATING,也就是更新中的意思,冻结当前订单的操作,而不是直接修改为支付成功。
库存服务设置冻结库存,可以扩展字段,也可以额外添加新的库存冻结表。
积分服务和库存一样,添加一个预增加积分,比如本次订单积分是 100,添加一个额外的存储表示等待增加的积分,账户余额服务等也是一样的操作。
Confirm 操作
Confirm 操作就是把前边的 Try 操作锁定的资源提交,类比数据库事务中的 Commit 操作。在支付的场景中,包括订单状态从准备中更新为支付成功;库存数据扣减冻结库存,积分数据增加预增加积分。
Cancel 操作
Cancel 操作执行的是业务上的回滚处理,类比数据库事务中的 Rollback 操作。首先订单服务,撤销预备状态,还原为待支付状态或者已取消状态,库存服务删除冻结库存,添加到可销售库存中,积分服务也是一样,将预增加积分扣减掉。
执行业务操作
下面来分析业务的实际执行操作,首先业务请求过来,开始执行 Try 操作,如果 TCC 分布式事务框架感知到各个服务的 Try 阶段都成功了以后,就会执行各个服务的 Confirm 逻辑。
如果 Try 阶段有操作不能正确执行,比如订单失效、库存不足等,就会执行 Cancel 的逻辑,取消事务提交。
7.MySQL 数据库如何实现 XA (内部分布式事务)规范
MySQL 有哪些一致性日志
如果 MySQL 数据库断电了,未提交的事务怎么办?
依靠日志,因为在执行一个操作之前,数据库会首先把这个操作的内容写入到文件系统日志里记录起来,然后再进行操作。当宕机或者断电的时候,即使操作并没有执行完,但是日志在操作前就已经写好了,我们仍然可以根据日志的内容来进行恢复。
MySQL InnoDB 引擎中和一致性相关的有:
重做日志(redo log)
回滚日志(undo log)
二进制日志(binlog)
**redo 日志,**每当有操作执行前,在数据真正更改前,会先把相关操作写入 redo 日志。这样当断电,或者发生一些意外,导致后续任务无法完成时,待系统恢复后,可以继续完成这些更改。
和 redo 日志对应的 undo 日志,也叫撤消日志,记录事务开始前数据的状态,当一些更改在执行一半时,发生意外而无法完成,就可以根据撤消日志恢复到更改之前的状态。
举个例子,事务 T1 更新数据 X,对 X 执行 Update 操作,从 10 更新到 20,对应的 Redo 日志为<T1,X,20> ,Undo<T1,X,10> 日志为 。
binlog 日志是 MySQL sever 层维护的一种二进制日志,是 MySQL 最重要的日志之一,它记录了所有的 DDL 和 DML 语句,除了数据查询语句 select、show 等,还包含语句所执行的消耗时间。
binlog 与 InnoDB 引擎中的 redo/undo log 不同,binlog 的主要目的是复制和恢复,用来记录对 MySQL 数据更新或潜在发生更新的 SQL 语句,并以事务日志的形式保存在磁盘中。
binlog 主要应用在 MySQL 的主从复制过程中,MySQL 集群在 Master 端开启 binlog,Master 把它的二进制日志传递给 slaves 节点,再从节点回放来达到 master-slave 数据一致的目的。
//查看binlog文件的内容
show binlog events;//查看指定binlog文件的内容
show binlog events in 'MySQL-bin.000001';//查看正在写入的binlog文件
show master status\G//获取binlog文件列表
show binary logs;
XA 规范是如何定义的
XA 是由 X/Open 组织提出的分布式事务规范,XA 规范主要定义了事务协调者(Transaction Manager)和 资源管理器(Resource Manager)之间的接口。
事务协调者(Transaction Manager)
因为 XA 事务是基于两阶段提交协议的,所以需要有一个协调者,来保证所有的事务参与者都完成了准备工作,也就是 2PC 的第一阶段。如果事务协调者收到所有参与者都准备好的消息,就会通知所有的事务都可以提交,也就是 2PC 的第二阶段。
之所以需要引入事务协调者,是因为在分布式系统中,两台机器理论上无法达到一致的状态,需要引入一个单点进行协调。协调者,也就是事务管理器控制着全局事务,管理事务生命周期,并协调资源。
资源管理器(Resource Manager)
负责控制和管理实际资源,比如数据库或 JMS 队列。
目前,主流数据库都提供了对 XA 的支持,在 JMS 规范中,即 Java 消息服务(Java Message Service)中,也基于 XA 定义了对事务的支持。
XA 事务的执行流程
XA 事务是两阶段提交的一种实现方式,根据 2PC 的规范,XA 将一次事务分割成了两个阶段,即 Prepare 和 Commit 阶段。
Prepare 阶段
TM 向所有 RM 资源管理器发送 prepare 指令,RM 接受到指令后,执行数据修改和日志记录等操作,然后返回可以提交或者不提交的消息给 TM。
如果事务协调者 TM收到所有参与者都准备好的消息,会通知所有的事务提交,然后进入第二阶段。
Commit 阶段
TM 接受到所有 RM 的 prepare 结果,如果有 RM 返回是不可提交或者超时,那么向所有 RM 发送 Rollback 命令;
如果所有 RM 都返回可以提交,那么向所有 RM 发送 Commit 命令,完成一次事务操作。
MySQL 如何实现 XA 规范
MySQL 中 XA 事务有两种情况,内部 XA 和外部 XA,其区别是事务发生在 MySQL 服务器单机上,还是发生在多个外部节点间上。
内部 XA
在 MySQL 的 InnoDB 存储引擎中,开启 binlog 的情况下,MySQL 会同时维护 binlog 日志与 InnoDB 的 redo log,为了保证这两个日志的一致性,MySQL 使用了 XA 事务,由于是在 MySQL 单机上工作,所以被称为内部 XA。
内部 XA 事务由 binlog 作为协调者,在事务提交时,则需要将提交信息写入二进制日志,也就是说,binlog 的参与者是 MySQL 本身。
外部 XA
外部 XA 就是典型的分布式事务,MySQL 支持 XA START/END/PREPARE/Commit 这些 SQL 语句,通过使用这些命令,可以完成分布式事务。
MySQL 外部 XA 主要应用在数据库代理层,实现对 MySQL 数据库的分布式事务支持,例如开源的数据库中间层,比如淘宝的 TDDL、阿里巴巴 B2B 的 Cobar 等。外部 XA 一般是针对跨多 MySQL 实例的分布式事务,需要应用层作为协调者,比如我们在写业务代码,在代码中决定提交还是回滚,并且在崩溃时进行恢复。
Binlog 中的 Xid
**当事务提交时,在 binlog 依赖的内部 XA 中,额外添加了 Xid 结构,binlog 有多种数据类型,包括以下三种:
- statement 格式,记录为基本语句,包含 Commit
- row 格式,记录为基于行
- mixed 格式,日志记录使用混合格式
不论是 statement 还是 row 格式,binlog 都会添加一个XID_EVENT 作为事务的结束,该事件记录了事务的 ID 也就是 Xid,在 MySQL 进行崩溃恢复时根据 binlog 中提交的情况来决定如何恢复。
Binlog 同步过程
Binlog 下的事务提交过程,整体过程是先写 redo log,再写 binlog,并以 binlog 写成功为事务提交成功的标志。
当有事务提交时:
- 第一步,InnoDB 进入 Prepare 阶段,并且 write/sync redo log,写 redo log,将事务的 XID 写入到 redo 日志中,binlog 不作任何操作;
- 第二步,进行 write/sync Binlog,写 binlog 日志,也会把 XID 写入到 Binlog;
- 第三步,调用 InnoDB 引擎的 Commit 完成事务的提交,将 Commit 信息写入到 redo 日志中。
- 如果是在第一步和第二步失败,则整个事务回滚;如果是在第三步失败,则 MySQL 在重启后会检查 XID 是否已经提交,若没有提交,也就是事务需要重新执行,就会在存储引擎中再执行一次提交操作,保障 redo log 和 binlog 数据的一致性,防止数据丢失。
在实际执行中,还牵扯到操作系统缓存 Buffer 何时同步到文件系统中,所以 MySQL 支持用户自定义在 Commit 时如何将 log buffer 中的日志刷到 log file 中,通过变量 innodb_flush_log_at_trx_Commit 的值来决定。