MySQL分库分表-多实例INSERT的困扰(9th)

HH MySQL1 13,7787字数 3456阅读11分31秒阅读模式

存在问题

分库分表是完成了,细心的朋友可能会发现。我们这边存在一个问题就是本来应该在一起的事务,现在因为分库事务将被分成了两个。如果第一个事务完成了提交,这时候应用程序或服务器发生了问题导致第二个没有提交。这样就有问题了。

例如:在之前文章的示例中导购的销售订单的事务已经提交了,而这时由于某些原因导致购买者的订单没有提交。这时候就会出现导购能看得到订单而消费者却看不到。文章源自运维生存时间-https://www.ttlsa.com/mysql/mysql-distributed-database-and-table-insert-trouble/

解决办法

1、直接使用MySQL的XA(分布式事物)文章源自运维生存时间-https://www.ttlsa.com/mysql/mysql-distributed-database-and-table-insert-trouble/

2、从业务上去解决文章源自运维生存时间-https://www.ttlsa.com/mysql/mysql-distributed-database-and-table-insert-trouble/

 文章源自运维生存时间-https://www.ttlsa.com/mysql/mysql-distributed-database-and-table-insert-trouble/

使用MySQL的XA前需要知道的文章源自运维生存时间-https://www.ttlsa.com/mysql/mysql-distributed-database-and-table-insert-trouble/

在同一个实例中不能有相同的XID(分布式事务ID)出现。使用Python在同一实例创建多个相同的XA会报错"The XID already exists"。文章源自运维生存时间-https://www.ttlsa.com/mysql/mysql-distributed-database-and-table-insert-trouble/

使用MySQL的XA文章源自运维生存时间-https://www.ttlsa.com/mysql/mysql-distributed-database-and-table-insert-trouble/

由于之前为了方便只在一个实例中演示了分库分表的操作。如果使用多个XA会报错的。文章源自运维生存时间-https://www.ttlsa.com/mysql/mysql-distributed-database-and-table-insert-trouble/

这边我只使(用伪代码)的形式来说明使用XA。如果有想知道Python如何使用XA可以常考以下链接文章源自运维生存时间-https://www.ttlsa.com/mysql/mysql-distributed-database-and-table-insert-trouble/

http://stackoverflow.com/questions/18485895/python-distributed-transactions-like-java-transaction-api-jta文章源自运维生存时间-https://www.ttlsa.com/mysql/mysql-distributed-database-and-table-insert-trouble/

# 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事务在性能上肯定会比没有使用事务来的差。文章源自运维生存时间-https://www.ttlsa.com/mysql/mysql-distributed-database-and-table-insert-trouble/

MySQL XA事务额外注意事项文章源自运维生存时间-https://www.ttlsa.com/mysql/mysql-distributed-database-and-table-insert-trouble/

在使用MySQL 5.6版本之前(包括5.6)使用分布式事务的时候如果还没有COMMIT的事务是不会记录到binlog中的。因此如果在COMMIT之前如果发生了MySQL挂掉,一定要将之前还没有提交的XA事务给回滚了。如果提交了由于没有些binlog导致从库就同步不到这次事务,从而导致了主从不一致的情况。而在MySQL5.7中解决了这一个问题。文章源自运维生存时间-https://www.ttlsa.com/mysql/mysql-distributed-database-and-table-insert-trouble/

从业务上去解决

从业务上去解决说白了就是能用语言来掩饰会遇到的BUG。然后,让那些做产品的能够接受,并且最后能有一些友好的补救措施。当然,这种方式不能影响主要业务。文章源自运维生存时间-https://www.ttlsa.com/mysql/mysql-distributed-database-and-table-insert-trouble/

问题分析文章源自运维生存时间-https://www.ttlsa.com/mysql/mysql-distributed-database-and-table-insert-trouble/

我们现在担心的是。当用户下单的时候发生了只有出售者的用户订单是写入数据库的,而购买订单没有写入到数据库。从表现来看就是出售者能看到订单信息,而购买者就看不到。并且这样的事一般只会在应用程序或服务器出问题容易发生,平常都是能正常的。文章源自运维生存时间-https://www.ttlsa.com/mysql/mysql-distributed-database-and-table-insert-trouble/

认真想一下,购买者没看到订单,那就不能够付款。不能够付款,说明订单的状态始终是未付款状态。也就是成了一个僵尸单。买家没有经济上的损失,卖家也没有货物上的损失。那这种情况是不是双方都能接受的嘞?这就需要和产品沟通了。文章源自运维生存时间-https://www.ttlsa.com/mysql/mysql-distributed-database-and-table-insert-trouble/

当然,我们不能放着这些僵尸单不管的。我们需要定期的去监控这些僵尸单的存在。如果检测到了僵尸单应该做出相应的处理,这时候又要和产品沟通了,是恢复该僵尸单让购买者能看到并给相关提示呢?还是该僵尸单给干掉,或者其他更友好的办法。文章源自运维生存时间-https://www.ttlsa.com/mysql/mysql-distributed-database-and-table-insert-trouble/

对僵尸单的监控文章源自运维生存时间-https://www.ttlsa.com/mysql/mysql-distributed-database-and-table-insert-trouble/

很容易想到可以通过查询数据库来过滤订单僵尸订单,这其实是一个办法。还有另外一个办法,就是在提交订单事物完成和会写相关的日志。我们只要去分析日志就好了。文章源自运维生存时间-https://www.ttlsa.com/mysql/mysql-distributed-database-and-table-insert-trouble/

这边我以日志的形式进行讲解:

1、当卖家订单提交是将信息记入到文件(主要是记录订单ID)。

2、当买家订单提交是将信息记录到另一个文件(主要是记录订单ID)。

3、用程序分析两个文件取出订单是否产生差集。并记录下差集的订单ID。这个差集的订单ID有僵尸单的嫌疑,

4、当再寻找僵尸单的时候先对上次可以的僵尸单进行处理,如果没有在购买者的日志中那说明那就是僵尸单了。然后再接着进行分析对比。

需要注意的是要把控好监控的时间间隔,不能太短,比如 1s。这时购买者订单可能还没提交。这样就会错误判断为僵尸单。

 

当然,还有其他很多方法。只要思维活跃,东西都是靠人想出来的。

 

昵称:HH

QQ:275258836

ttlsa群交流沟通(QQ群②:6690706 QQ群③:168085569 QQ群④:415230207(新) 微信公众号:ttlsacom)

感觉本文内容不错,读后有收获?

逛逛衣服店,鼓励作者写出更好文章。

weinxin
我的微信
微信公众号
扫一扫关注运维生存时间公众号,获取最新技术文章~
HH
  • 本文由 发表于 03/03/2016 00:13:22
  • 转载请务必保留本文链接:https://www.ttlsa.com/mysql/mysql-distributed-database-and-table-insert-trouble/
评论  1  访客  1
    • 锟斤拷
      锟斤拷 9

      搞那么求麻烦干啥 mycat搞到。。。。

    评论已关闭!