找回密码
 立即注册
首页 业界区 安全 mysql数据库事务的实现和XA事务

mysql数据库事务的实现和XA事务

圉棺 4 天前
一、事务的实现

1.1、事务的执行流程

1.png

根据上图,事务的执行流程如下:
①查询操作先从Buffer Pool中查询数据,若存在则直接输出,不存在则读取磁盘中的数据并放入Buffer Pool;
②在操作任何数据之前,会先将数据的旧值写入undo log日志文件中,以便执行事务过程中出现异常后好回滚到事务执行之前的数据;
③然后再操作Buffer Pool(内存)中的数据并设置成脏页,同时将操作的数据写入到Redo Log Buffer;
④将Redo Log Buffer中的数据写入到os cache系统内核缓存中;
⑤系统内核调用fsync()将os buffer中的数据写入到Redo Log文件中,并设置状态为prepare;
⑥Redolog文件写入完成后,由server层将执行的操作性修改,包括数据库结构和表数据的变更,但不包括select语句,写入到binlog文件中;
⑦binlog写入成功后,设置状态为commit;
⑧最后完成事务提交。
1.2、重做日志(Redo Log)—— 实现了事务的持久性

  重做日志,记录的是事务提交时数据页的物理变化,服务宕机可以用来同步数据。该日志文件由两部分:重做日志缓冲(内存中)(redo log buffer)以及重做日志文件(磁盘中)(redo log file)。当事务提交之后会把所有修改信息都存到该日志文件中,用于在刷新脏页(被修改了数据的内存页)到磁盘的过程中发生错误时,进行数据恢复使用。
1.3、回滚日志(Undo Log)—— 实现了事务的原子性和一致性

  回滚日志,用于记录数据被修改前的信息,作用包含2个:
①、提供回滚和MVCC(多版本并发控制)。
②、undo log和redo log记录物理日志不一样,undo log记录的是逻辑日志,当事务回滚时,通过逆操作恢复原来的数据。
  可以认为当delete一条记录时,undo log中会记录一条对应的insert记录,反之亦然,当update一条记录时,他记录一条对应相反的update记录。当执行rollback时,就可以从undo log中的逻辑记录读取到相应的内容并进行回滚。
二、XA事务

2.1、MySQL 数据库的分布式XA事务

  XA 事务允许不同数据库之间的分布式事务,如一台服务器是MySQL 数据库的,另一台是Oracle 数据库的,又可能还有一台服务器是SQL Server 数据库的,只要参与在全局事务中的每个节点都支持XA 事务。
  XA 事务由1个或多个资源管理器(Resource Managers)、1个事务管理器(Transaction Manager)以及1个应用程序(Application Program)组成,这3个角色是国际开放标准组织 Open Group 定义的分布式事务处理模型DTP(Distributed Transaction Processing Reference Model)中规定的3个角色,分别如下:
2.png

①、AP(Application Program):即应用程序,可以理解为使用 DTP 分布式事务的程序。
②、RM's(Resource Manager):即资源管理器,可以理解为事务的参与者,一般情况下是指一个数据库实例,通过资源管理器对该数据库进行控制,资源管理器控制着分支事务。
③、TM(Transaction Manager):事务管理器,负责协调和管理事务,事务管理器控制着全局事务,管理事务生命周期,并协调各个 RM。全局事务是指分布式事务处理环境中,需要操作多个数据库共同完成一个工作,这个工作即是一个全局事务。
  XA分布式事务使用两段式提交( two-phase commit )的方式。
①、在第一阶段,所有参与全局事务的节点都开始准备( PREPARE ),告诉事务管理器它们准备好提交了。
②、在第二阶段,事务管理器告诉资源管理器执行ROLLBACK 还是COMMIT。如果任何一个节点显示不能提交,则所有的节点都被告知需要回滚。可见与本地事务不同的是,分布式事务需要多一次的PREPARE 操作,待收到所有节点的同意信息后,再进行COMMIT 或是ROLLBACK 操作。
  这种机制特别适用于需要跨多个数据库或其他资源管理器保持一致性的场景,例如银行转账、电子商务订单处理等。然而,XA事务有2个性能问题:
①、性能开销较大,因为它涉及到更多的协调步骤,并且可能会导致阻塞问题;
②、XA事务在执行过程中,所有参与的RM(资源管理器,也就是数据库)会对其操作的表加锁,导致其它XA事务或非XA事务需要阻塞等待该XA事务释放锁;
因此,在设计系统时需要权衡使用XA事务的成本与收益。
2.1.1、mysql数据库中XA事务的相关命令
  1. #innodb存储引擎开启支持分布式事务
  2. set global innodb_support_ax=on
  3. #开启一个新的XA事务,并为这个XA事务分配一个全局唯一的事务标识xid
  4. #在这个命令之后,所有后续的SQL操作都会被包含在这个xid的XA事务中,直到遇到XA END命令
  5. XA {START|BEGIN} xid [JOIN|RESUME]
  6. #将事务标识为xid的XA事务结束(也就是设置该事务为IDLE状态)
  7. #这并不意味着事务标识为xid的XA事务已经完成或提交,它只是表明事务标识为xid的XA事务不再接受新的操作。在XA END之后,不能再对该事务进行任何修改操作。
  8. XA END xid [SUSPEND [FOR MIGRATE]]
  9. #将事务标识为xid的XA事务设置为PREPARE状态
  10. #这是2阶段提交(2PC, Two-Phase Commit)的第1阶段。在这个阶段,所有参与的资源管理器会投票决定是否可以安全地提交该事务。如果所有参与者都准备好提交,则可以进入下一阶段;如果有任何一个参与者不能准备好,则整个事务会被回滚。
  11. XA PREPARE xid
  12. #提交事务标识为xid的XA事务
  13. XA COMMIT xid [ONE PHASE]
  14. #回滚事务标识为xid的XA事务
  15. XA ROLLBACK xid
  16. #列出所有处于预备状态但尚未完成提交或回滚的XA事务。
  17. #这个命令对于恢复未完成的XA事务非常有用,特别是在系统崩溃后需要手动干预来解决悬挂事务时。
  18. XA RECOVER [CONVERT XID]
复制代码

  • XA RECOVER中各个字段的含义
    3.png

gtrid、bqual、formatId(XA RECOVER命令只返回了gtrid字段和bqual字段的长度)这3个字段是 JTA/XA 分布式事务中用于标识事务的必要部分,通常封装在 Xid 接口中。各个字段的含义如下所示:
字段全称含义gtridGlobal Transaction ID全局事务唯一标识符,在整个分布式系统中必须唯一。bqualBranch Qualifier分支限定符,表示某个RM(资源管理器,也就是数据库)在这个XA事务中的唯一标识,同一个XA事务可能被多个RM(资源管理器,也就是数据库)参与,每个RM有唯一的 bqual,默认值是长度为0的空字符串""。formatIdFormat Identifier格式标识符,定义 gtrid 和 bqual 的编码格式,默认值为0formatId定义了 gtrid 和 bqual 的数据格式或命名规则,常见的formatId的取值如下:
formatId的取值含义 / 使用场景0x544d 或 21549BEA Tuxedo 标准格式(老式企业级系统)1 Atomikos默认使用(流行的开源分布式事务管理器)0表示“自定义”或“默认格式”(常用于测试)xxxx…开发者自定义格式,表示“这是我系统的特定编码方式”2.1.2、JAVA实现分布式XA事务

如下所示:
  1. //引入jar包
  2. <dependency>
  3.     <groupId>mysql</groupId>
  4.     mysql-connector-java</artifactId>
  5.     <version>8.0.28</version>
  6. </dependency>
复制代码
  1. package com.distribute;
  2. import com.mysql.cj.jdbc.JdbcConnection;
  3. import com.mysql.cj.jdbc.MysqlXAConnection;
  4. import com.mysql.cj.jdbc.MysqlXid;
  5. import javax.transaction.xa.XAException;
  6. import javax.transaction.xa.XAResource;
  7. import java.sql.*;
  8. import java.util.UUID;
  9. ...省略class的定义...
  10. private static void testXA() {
  11.   Connection connection1 = null;
  12.   Connection connection2 = null;
  13.   MysqlXAConnection xa1 = null;
  14.   MysqlXAConnection xa2 = null;
  15.   Statement statement1 = null;
  16.   Statement statement2 = null;
  17.   try {
  18.      //获取两台MySQL的连接
  19.      connection1 = DriverManager.getConnection("jdbc:mysql://192.168.86.111:3306/db3", "root", "123456");
  20.      connection2 = DriverManager.getConnection("jdbc:mysql://192.168.86.112:3306/db1", "root", "123456");
  21.      //获取XA连接 true表示打印日志
  22.      xa1 = new MysqlXAConnection((JdbcConnection) connection1, true);
  23.      xa2 = new MysqlXAConnection((JdbcConnection) connection2, true);
  24.      //获取事务管理器
  25.      XAResource resourceManager1 = xa1.getXAResource();
  26.      XAResource resourceManager2 = xa2.getXAResource();
  27.      //构造组成xid的三部分
  28.      byte[] gtrid = UUID.randomUUID().toString().replace("-", "").getBytes();
  29.      byte[] bqual = "分支1".getBytes();
  30.      byte[] bqua2 = "分支2".getBytes();
  31.      int formatId = 1;
  32.      //通过(gtrid, bqual, formatId)获取xid对象(有一种算法是将这3个字段分别取16进制拼接起来组成xid)
  33.      MysqlXid xid1 = new MysqlXid(gtrid, bqual, formatId);
  34.      MysqlXid xid2 = new MysqlXid(gtrid, bqua2, formatId);
  35.      //要执行的业务sql
  36.      String sql1 = "insert into test(`name`, `age`) values('竹子', 23)";
  37.      String sql2 = "insert into employee(`employee_name`, `department_id`) values('竹叶', 1)";
  38.      //执行XA {START|BEGIN} xid [JOIN|RESUME]命令
  39.      resourceManager1.start(xid1, XAResource.TMNOFLAGS);
  40.      resourceManager2.start(xid2, XAResource.TMNOFLAGS);
  41.      //执行业务sql
  42.      statement1 = connection1.createStatement();
  43.      statement1.execute(sql1);
  44.      statement2 = connection2.createStatement();
  45.      statement2.execute(sql2);
  46.      //执行XA END xid [SUSPEND [FOR MIGRATE]]命令
  47.      resourceManager1.end(xid1, XAResource.TMSUCCESS);
  48.      resourceManager2.end(xid2, XAResource.TMSUCCESS);
  49.      //执行XA PREPARE xid命令
  50.      int prepare1 = resourceManager1.prepare(xid1);
  51.      int prepare2 = resourceManager2.prepare(xid2);
  52.      //是否只存在一台MySQL,如果只存在一台MySQL,那么就不需要进行分布式的二阶段提交了
  53.      boolean onePhase = false;
  54.      //都准备好了
  55.      if (prepare1 == XAResource.XA_OK && prepare2 == XAResource.XA_OK) {
  56.         //执行XA COMMIT xid [ONE PHASE]命令
  57.         resourceManager1.commit(xid1, onePhase);
  58.         resourceManager2.commit(xid2, onePhase);
  59.      } else {
  60.         //执行XA ROLLBACK xid命令
  61.         resourceManager1.rollback(xid1);
  62.         resourceManager2.rollback(xid2);
  63.      }
  64.   } catch (SQLException | XAException e) {
  65.      e.printStackTrace();
  66.   } finally {
  67.      try {
  68.         statement1.close();
  69.         statement2.close();
  70.         xa1.close();
  71.         xa2.close();
  72.         connection1.close();
  73.         connection2.close();
  74.      } catch (SQLException throwables) {
  75.         throwables.printStackTrace();
  76.      }
  77.   }
  78. }
复制代码
程序的运行结果如下:
4.png

上述代码中的AP(Application Program)、RM(Resource Manager)、TM(Transaction Manager)的关系如下:
5.png

2.2、MySQL 数据库的内部XA事务——解决了主从同步过程中的数据一致性问题

  由于复制的需要,因此目前绝大多数的数据库都开启了binlog 功能。在事务提交时,先写二进制日志,再写InnoDB 存储引擎的重做日志(Redo Log)。对上述两个操作的要求也是原子的,即binlog 和Redo Log必须同时写人。若binlog先写了,而在写人InnoDB 存储引擎时发生了宕机,那么slave 可能会接收到master 传过去的binlog 并执行,最终导致了主从不一致的情况。如图所示:
6.png

如果执行完①、②后在步骤③之前MySQL 数据库发生了宕机,则会发生主从不一致的情况。为了解决这个问题, MySQL 数据库在binlog 与InnoDB 存储引擎之间采用XA 事务。当事务提交时,InnoDB 存储引擎会先做一个PREPARE 操作,将事务的xid 写人,接着进行binlog的写入,如下所示:
7.png

如果在InnoDB 存储引擎提交前, MySQL 数据库岩机了,那么MySQL 数据库在重启后会先检查准备的XID 事务是否已经提交,若没有,则在存储引擎层再进行一次提交操作。
三、在5.7.7和之后的版本中使用MySQL的XA事务,存在的坑点

①、XA 事务不能完全适应二进制日志的意外停止。如果在服务器正在执行 XA PREPARE、XA COMMIT、XA ROLLBACK 或 XA COMMIT ... ONE PHASE 语句时出现意外停止,则服务器可能无法恢复到正确状态,从而导致服务器和二进制日志处于不一致的状态。在这种情况下,二进制日志可能包含未应用的额外 XA 事务,或遗漏已应用的 XA 事务。此外,如果启用了 GTID,则在恢复后 @@GLOBAL.GTID_EXECUTED 可能无法正确描述已应用的事务。请注意,如果在 XA PREPARE 之前、XA PREPARE 和 XA COMMIT(或 XA ROLLBACK)之间或 XA COMMIT(或 XA ROLLBACK)之后发生意外停止,则服务器和二进制日志将正确恢复并进入一致状态;
②、不支持将复制过滤器或二进制日志过滤器与 XA 事务结合使用。过滤表可能会导致副本上的 XA 事务为空,并且不支持空 XA 事务。此外,通过在副本上设置 master_info_repository=TABLE 和 relay_log_info_repository=TABLE(在 MySQL 8.0 中成为默认设置),数据引擎事务的内部状态会在过滤的 XA 事务之后发生更改,并且可能与复制事务上下文状态不一致.每当 XA 事务受到复制过滤器的影响时,无论该事务是否因此为空,都会记录错误 ER_XA_REPLICATION_FILTERS。如果事务不为空,则副本能够继续运行,但您应该采取措施停止对 XA 事务使用复制过滤器以避免潜在问题。如果事务为空,则副本停止。在这种情况下,副本可能处于未确定状态,其中复制过程的一致性可能会受到影响。特别是,副本的副本上的 gtid_executed 集可能与源上的不一致。要解决这种情况,请隔离源并停止所有复制,然后检查复制拓扑中的 GTID 一致性。撤消生成错误消息的 XA 事务,然后重新启动复制;
③、在 MySQL 5.7.19 之前,FLUSH TABLES WITH READ LOCK 与 XA 事务不兼容;
④、XA 事务对于基于语句的复制被认为是不安全的。如果在数据源上并行提交的两个 XA 事务正在以相反的顺序在副本上准备,则可能会发生无法安全解决的锁定依赖关系,并且复制可能会因副本上的死锁而失败。这种情况可能发生在单线程或多线程副本上。当设置 binlog_format=STATEMENT 时,会针对 XA 事务中的 DML 语句发出警告。当 binlog_format=MIXED 或 binlog_format=ROW 设置时,XA 事务中的 DML 语句使用基于行的复制进行记录,并且不存在潜在问题。
详情可以参考官网https://dev.mysql.com/doc/refman/5.7/en/xa-restrictions.html
参考资料:
https://blog.csdn.net/sunboylife/article/details/147981118
《mysql技术内幕Innodb存储引擎》

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册