分库分表是完成了,细心的朋友可能会发现。我们这边存在一个问题就是本来应该在一起的事务,现在因为分库事务将被分成了两个。如果第一个事务完成了提交,这时候应用程序或服务器发生了问题导致第二个没有提交。这样就有问题了。
例如:在之前文章的示例中导购的销售订单的事务已经提交了,而这时由于某些原因导致购买者的订单没有提交。这时候就会出现导购能看得到订单而消费者却看不到。
1、直接使用MySQL的XA(分布式事物)
2、从业务上去解决
使用MySQL的XA前需要知道的
在同一个实例中不能有相同的XID(分布式事务ID)出现。使用Python在同一实例创建多个相同的XA会报错”The XID already exists”。
使用MySQL的XA
由于之前为了方便只在一个实例中演示了分库分表的操作。如果使用多个XA会报错的。
这边我只使(用伪代码)的形式来说明使用XA。如果有想知道Python如何使用XA可以常考以下链接
http://stackoverflow.com/questions/18485895/python-distributed-transactions-like-java-transaction-api-jta
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | # 1.获取买家和卖家数据库连接描述符 buy_user_conn_info = get_conn_info ( ) sell_user_conn_info = get_conn_info ( ) # 比较 得到的两个数据库连接描述符 if buy_user_conn_info == sell_user_conn_info : # 如果相等使用同只创建一个数据量连接进行数据库操作 conn = mysql . connector . connect ( * * sell_user_conn_info ) # 通过pysnowflask获得XID, order_id。XID和order_id是同一个 guid = snowflake . client . get_guid ( ) # 获得游标 cur = conn . cursor ( ) ; # 开始一个XA事务 cur . execute ( “XA START ‘{xid}'” . format ( xid = guid ) ) ; # 生成出售者订单信息 cur . execute ( “INSERT INTO test_x.sell_order_x VALUES(xx)” ) ; cur . execute ( “INSERT INTO test_x.order_goods_x VALUES(xx)” ) ; cur . execute ( “INSERT INTO test_x.order_goods_x VALUES(xx)” ) ; # 生成购买者订单信息 cur . execute ( “INSERT INTO test_y.buy_order_y VALUES(xx)” ) ; # 完成数据操作 cur . execute ( “XA END ‘{xid}'” . format ( xid = guid ) ) ; # 要准备提交了 cur . execute ( “XA PREPARE ‘{xid}'” . format ( xid = guid ) ) ; # 提交事务 cur . execute ( “XA COMMIT ‘{xid}'” . format ( xid = guid ) ) ; else : # 如果连接描述符不相等就需要在不实例中使用XID相等的XA事务 conn_sell = mysql . connector . connect ( * * sell_user_conn_info ) conn_buy = mysql . connector . connect ( * * buy_user_conn_info ) # 获得游标 cur_sell = conn_sell . cursor ( ) ; cur_buy = conn_buy . cursor ( ) ; try : # 通过pysnowflask获得XID, order_id。XID和order_id是同一个 cur_sell . execute ( “XA START ‘{xid}'” . format ( xid = guid ) ) ; cur_buy . execute ( “XA START ‘{xid}'” . format ( xid = guid ) ) ; # 生成出售者订单信息 cur_sell . execute ( “INSERT INTO test_x.sell_order_x VALUES(xx)” ) ; cur_sell . execute ( “INSERT INTO test_x.order_goods_x VALUES(xx)” ) ; cur_sell . execute ( “INSERT INTO test_x.order_goods_x VALUES(xx)” ) ; # 生成购买者订单信息 cur_buy . execute ( “INSERT INTO test_y.buy_order_y VALUES(xx)” ) ; # 要准备提交了 cur_sell . execute ( “XA PREPARE ‘{xid}'” . format ( xid = guid ) ) ; cur_buy . execute ( “XA PREPARE ‘{xid}'” . format ( xid = guid ) ) ; # 提交事务 cur_sell . execute ( “XA COMMIT ‘{xid}'” . format ( xid = guid ) ) ; cur_buy . execute ( “XA COMMIT ‘{xid}'” . format ( xid = guid ) ) ; except : # 发生错误回滚XA事务 cur_sell . execute ( “XA ROLLBACK ‘{xid}'” . format ( xid = guid ) ) ; cur_buy . execute ( “XA ROLLBACK ‘{xid}'” . format ( xid = guid ) ) ; finally : cur_sell . close ( ) cur_buy . close ( ) conn_sell . disconnect ( ) conn_buy . disconnect ( ) |
需要说明的是如果使用了XA事务在性能上肯定会比没有使用事务来的差。
MySQL XA事务额外注意事项
在使用MySQL 5.6版本之前(包括5.6)使用分布式事务的时候如果还没有COMMIT的事务是不会记录到binlog中的。因此如果在COMMIT之前如果发生了MySQL挂掉,一定要将之前还没有提交的XA事务给回滚了。如果提交了由于没有些binlog导致从库就同步不到这次事务,从而导致了主从不一致的情况。而在MySQL5.7中解决了这一个问题。
从业务上去解决说白了就是能用语言来掩饰会遇到的BUG。然后,让那些做产品的能够接受,并且最后能有一些友好的补救措施。当然,这种方式不能影响主要业务。
问题分析
我们现在担心的是。当用户下单的时候发生了只有出售者的用户订单是写入数据库的,而购买订单没有写入到数据库。从表现来看就是出售者能看到订单信息,而购买者就看不到。并且这样的事一般只会在应用程序或服务器出问题容易发生,平常都是能正常的。
认真想一下,购买者没看到订单,那就不能够付款。不能够付款,说明订单的状态始终是未付款状态。也就是成了一个僵尸单。买家没有经济上的损失,卖家也没有货物上的损失。那这种情况是不是双方都能接受的嘞?这就需要和产品沟通了。
当然,我们不能放着这些僵尸单不管的。我们需要定期的去监控这些僵尸单的存在。如果检测到了僵尸单应该做出相应的处理,这时候又要和产品沟通了,是恢复该僵尸单让购买者能看到并给相关提示呢?还是该僵尸单给干掉,或者其他更友好的办法。
对僵尸单的监控
很容易想到可以通过查询数据库来过滤订单僵尸订单,这其实是一个办法。还有另外一个办法,就是在提交订单事物完成和会写相关的日志。我们只要去分析日志就好了。
这边我以日志的形式进行讲解:
1、当卖家订单提交是将信息记入到文件(主要是记录订单ID)。
2、当买家订单提交是将信息记录到另一个文件(主要是记录订单ID)。
3、用程序分析两个文件取出订单是否产生差集。并记录下差集的订单ID。这个差集的订单ID有僵尸单的嫌疑,
4、当再寻找僵尸单的时候先对上次可以的僵尸单进行处理,如果没有在购买者的日志中那说明那就是僵尸单了。然后再接着进行分析对比。
需要注意的是要把控好监控的时间间隔,不能太短,比如 1s。这时购买者订单可能还没提交。这样就会错误判断为僵尸单。
文章转载来自:trustauth.cn