在上篇文章 MySQL 事务的隔离级别 中已经提到了事务的特性、事务的隔离级别及各个隔离级别可能导致的问题,下面来说说MySQL中事务的使用
-
MySQL 事务简单使用
# 查看事务自动提交的模式 show [session] variables like 'autocommit'; //会话级别 show global variables like 'autocommit'; // 全局级别 # 关闭自动提交: set global autocommit=0; set [session] autocommit=0; # 开启自动提交: set global autocommit=1; set [session] autocommit=1; # 开启事务 begin; 或: start transaction; # 提交 commit; # 回滚 rollback;
在代码里使用事务前
- 需要先关闭自动提交
- 然后开启事务,
- 执行操作数据的SQL
- 如果中间SQL语句抛异常了,就调rollback 回滚,将数据恢复到事务开始前的状态,事务结束
- 如果中间SQL语句没有抛异常,就commit 提交,事务结束
-
MySQL 分布式事务 参考:MySQL 中基于 XA 实现的分布式事务
对于有关联的数据的处理有时候可能不在一个数据库中,这个时候如果想要保证数据绝对可靠,就需要考虑使用分布式事务了。
-
XA协议
-
XA规范中分布式事务有AP,RM,TM组成:
- 其中应用程序(Application Program ,简称AP):AP定义事务边界(定义事务开始和结束)并访问事务边界内的资源。
- 资源管理器(Resource Manager,简称RM):Rm管理计算机共享的资源,许多软件都可以去访问这些资源,资源包含比如数据库、文件系统、打印机服务器等。
- 事务管理器(Transaction Manager ,简称TM):负责管理全局事务,分配事务唯一标识,监控事务的执行进度,并负责事务的提交、回滚、失败恢复等。
-
Xa主要规定了RM与TM之间的交互,XA规范中定义的RM 和 TM交互的接口:
-
xa_start负责开启或者恢复一个事务分支,并且管理XID到调用线程
-
xa_end 负责取消当前线程与事务分支的关联
-
xa_prepare负责询问RM 是否准备好了提交事务分支
-
xa_commit通知RM提交事务分支
-
xa_rollback 通知RM回滚事务分支
-
-
XA协议使用了二阶段协议
- 第一阶段TM要求所有的RM准备提交对应的事务分支,询问RM是否有能力保证成功的提交事务分支,RM根据自己的情况,如果判断自己进行的工作可以被提交,那就对工作内容进行持久化,并给TM回执OK;否者给TM的回执NO。RM在发送了否定答复并回滚了已经的工作后,就可以丢弃这个事务分支信息了。
- 第二阶段TM根据阶段1各个RM prepare的结果,决定是提交还是回滚事务。如果所有的RM都prepare成功,那么TM通知所有的RM进行提交;如果有RM prepare回执NO的话,则TM通知所有RM回滚自己的事务分支。
-
-
MySQL中XA实现
-
通过 show engines; 可以看到 只有InnoDB存储引擎支持XA事务
-
在MySQL数据库分布式事务中,MySQL是XA事务过程中的资源管理器(RM)存在的,TM是连接MySQL服务器的客户端。MySQL数据库是作为RM存在的,在分布式事务中一般会涉及到至少两个RM。需要注意的是MySQL中只有当隔离级别为Serializable时候才能使用分布式事务,所以需要使用
set global tx_isolation='serializable';
设置数据库隔离级别 -
MySQL 中使用XA
show variables like ‘innodb_support_xa’; //查看是否开启了 XA 功能 XA {START|BEGIN} xid [JOIN|RESUME] //开启XA事务,如果使用的是XA START而不是XA BEGIN,那么不支持[JOIN|RESUME],xid是一个唯一值,表示事务分支标识符 XA END xid [SUSPEND [FOR MIGRATE]] //结束一个XA事务,不支持[SUSPEND [FOR MIGRATE]] XA PREPARE xid 准备提交 XA COMMIT xid [ONE PHASE] //提交,如果使用了ONE PHASE,则表示使用一阶段提交。两阶段提交协议中,如果只有一个RM参与,那么可以优化为一阶段提交 XA ROLLBACK xid //回滚 XA RECOVER [CONVERT XID] //列出所有处于PREPARE阶段的XA事务
-
其中首先使用XA START ‘xid' 启动了一个XA事务,并把它置于ACTIVE状态
-
对于一个ACTIVE状态的 XA事务,我们可以执行构成事务的多条SQL语句,也就是指定分支事务的边界,然后执行一个XA END ‘xid'语句,XA END把事务放入IDLE状态,也就是结束事务边界,在xa start和xa end之间的语句就构成了本分支事务的一个事务范围。当调用xa end 'xid1'后由于结束了事务边界,所以这时候如果再执行sql语句会抛出ERROR 1399 (XAE07): XAER_RMFAIL: The command cannot be executed when global transaction is in the IDLE state错误,也就是当分支事务处于IDLE状态时候不允许执行没有包含到分支事务边界里面的其他SQL.
-
对于一个IDLE 状态XA事务,可以执行一个XA PREPARE语句或一个XA COMMIT…ONE PHASE语句,其中XA PREPARE把事务放入PREPARED状态。在此点上的XA RECOVER语句将在其输出中包括事务的xid值,因为XA RECOVER会列出处于PREPARED状态的所有XA事务。XA COMMIT…ONE PHASE用于预备和提交事务,也就是转换为一阶段协议,直接提交事务。
-
对于一个PREPARED状态的 XA事务,可以执行XA COMMIT 语句来提交或者执行XA ROLLBACK来回滚xa事务。
-
-
-
分布式事务的几个解决方案 参考:对于 MySQL 分布式事务的看法
-
TCC补偿模式
TCC 方案是二阶段提交的 另一种实现方式,它涉及 3 个模块,主业务、从业务和 活动管理器(协作者)
-
第一阶段:主业务服务分别调用所有从业务服务的 Try 操作,并在活动管理器中记录所有从业务服务。当所有从业务服务 Try 成功或者某个从业务服务 Try 失败时,进入第二阶段。
-
第二阶段:活动管理器根据第一阶段从业务服务的 Try 结果来执行 Confirm 或 Cancel 操作。如果第一阶段所有从业务服务都 Try 成功,则协作者调用所有从业务服务的 Confirm 操作,否则,调用所有从业务服务的 cancel 操作。
-
在第二阶段中,Confirm 和 Cancel 同样存在失败情况,所以需要对这两种情况做异常处理以保证数据一致性。
-
Confirm 失败:则回滚所有 Confirm 操作并执行 Cancel 操作。Cancel 失败:从业务服务需要提供自动重试 Cancel 机制,以保证 Cancel 成功。
注:这种方案实现的要素在于,调用链需要被记录,且每个服务提供者都需要提供一组业务逻辑相反的操作,互为补偿,同时回滚操作和确认提交操作要保证幂等
-
-
消息队列可靠消息提交
利用消息队列,需要执行的操作的时候 产生一条可靠的消息到消息队列,然后可靠地消费这个消息。通过 消息队列的消息完成最终的一致性
以转账服务为例,当支付宝账户扣除1万后,我们只要生成一个凭证(消息)即可,这个凭证(消息)上写着“让余额宝账户增加 1万”,只要这个凭证(消息)能可靠保存,我们最终是可以拿着这个凭证(消息)让余额宝账户增加1万的,即我们能依靠这个凭证(消息)完成最终一致性。最为核心的问题便是如何可靠的保证消息会被消费以及如何解决消息重复投递的问题。
-
如何可靠保存凭证(消息):
1)支付宝在扣款事务提交之前,向实时消息服务请求发送消息,实时消息服务只记录消息数据,而不真正发送,只有消息发送成功后才会提交事务; ( 相当于先准备了一个消息,但是不能被消费)
2)当支付宝扣款事务被提交成功后,向实时消息服务确认发送。只有在得到确认发送指令后,实时消息服务才真正发送该消息;
3)当支付宝扣款事务提交失败回滚后,向实时消息服务取消发送。在得到取消发送指令后,该消息将不会被发送;
4)对于那些未确认的消息或者取消的消息,需要有一个定时任务 定时去支付宝系统查询这个消息的状态并进行更新。为什么需要这一步骤,举个例子:假设在第2步支付宝扣款事务被成功提交后,系统挂了,此时消息状态并未被更新为“确认发送”,从而导致消息不能被发送。
优点:消息数据独立存储,降低业务系统与消息系统间的耦合;
缺点:一次消息发送需要两次请求;业务处理服务需要实现消息状态回查接口。 -
如何解决消息重复投递的问题:
还有一个很严重的问题就是消息重复投递,以我们支付宝转账到余额宝为例,如果相同的消息被重复投递两次,那么我们余额宝账户将会增加2万而不是1万了。
为什么相同的消息会被重复投递?比如余额宝处理完消息msg后,发送了处理成功的消息给实时消息服务,正常情况下实时消息服务应该要删除消息msg,但如果实时消息服务这时候悲剧的挂了,重启后一看消息msg还在,就会继续发送消息msg。
解决方法很简单,消费消息做到幂等性,在余额宝这边增加消息应用状态表(message_apply),通俗来说就是个账本,用于记录消息的消费情况,每次来一个消息,在真正执行之前,先去消息应用状态表中查询一遍,如果找到说明是重复消息,丢弃即可,如果没找到才执行,同时插入到消息应用状态表(同一事务)。
-
-
阿里开源分布式中间件 Seata —— 标准分布式模型 TXC
-
Seata 内部定义了 3个模块来处理全局事务和分支事务的关系和处理过程,这三个组件分别是:
Transaction Coordinator (TC):事务协调器,维护全局事务的运行状态,负责协调并驱动全局事务的提交或回滚。
Transaction Manager (TM):事务的发起者,控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或全局回滚的决议。
Resource Manager (RM):负责控制每个服务的分支事务,负责分支注册、状态汇报,并接收事务协调器的指令,驱动分支(本地)事务的提交和回滚。 -
简要说说整个全局事务的执行步骤:
TM 向 TC 申请开启一个全局事务,TC 创建全局事务后返回全局唯一的 XID,XID 会在全局事务的上下文中传播;
RM 向 TC 注册分支事务,该分支事务归属于拥有相同 XID 的全局事务;
TM 向 TC 发起全局提交或回滚;
TC 调度 XID 下的分支事务完成提交或者回滚。 -
相对于XA ,Seata 优化了锁定的机制。RM 到 TC 控制器端查询操作的本地数据这一行是否被全局锁定了,如果被锁定了,就重新尝试,如果没被锁定,则加全局锁后开始解析 SQL,把业务数据在更新前后的数据镜像组织成回滚日志,并将 undo log 日志插入 undo_log 表中,保证每条更新数据的业务 sql 都有对应的回滚日志存在。这样做的好处就是,本地事务执行完可以立即释放本地事务锁定的资源,然后向 TC 上报分支状态。当 TM 决议全局提交时,就不需要同步协调处理了,TC 会异步调度各个 RM 分支事务删除对应的 undo log 日志和全局锁,这个步骤非常快速地可以完成;当 TM 决议全局回滚时,RM 收到 TC 发送的回滚请求,RM 通过 XID 找到对应的 undo log 回滚日志,然后执行回滚日志完成回滚操作。
分支事务中数据的 本地锁 由本地事务管理,在分支事务 Phase1 结束时释放。同时,随着本地事务结束,连接也得以释放。
分支事务中数据的 全局锁 在事务协调器侧 TC 管理,在决议 Phase2 全局提交时,全局锁马上可以释放。只有在决议全局回滚的情况下,全局锁才被持有至分支的 Phase2 结束。
这个设计,极大地减少了分支事务对资源(数据和连接)的锁定时间,给整体并发和吞吐的提升提供了基础。
-
-
-
注:本文参考多篇文章,可以作为一个了解的知识(奈何我水平太低,后面的内容我暂时还没能完全参透)。
本文整理不易,如需转载请注明出处, https://www.cnblogs.com/zhuchenglin/p/12716882.html