Mysql--基础知识点--105--分布式事务

MySQL 分布式事务主流方案对比与实战(修正版)

一、XA 协议(两阶段提交)

原理 :XA 规范定义了事务管理器(TM)与资源管理器(RM)的交互接口。MySQL 通过 XA STARTXA ENDXA PREPAREXA COMMIT / XA ROLLBACK 命令实现两阶段提交。

修正后的 Python 示例 (使用 mysql-connector-python 的原生 XA 命令):

python 复制代码
import mysql.connector
from mysql.connector import Error

def xa_transfer_example():
    conn1 = None
    conn2 = None
    xid = b'transfer_txn_001'  # XID 必须是字节串

    try:
        # 连接两个不同的 MySQL 数据库(或同一实例的不同库)
        conn1 = mysql.connector.connect(
            host='localhost', user='user1', password='pass1', database='db1')
        conn2 = mysql.connector.connect(
            host='localhost', user='user2', password='pass2', database='db2')
        
        # 开启 XA 事务(每个参与者独立执行 XA START)
        cursor1 = conn1.cursor()
        cursor1.execute(f"XA START '{xid.decode()}'")
        cursor2 = conn2.cursor()
        cursor2.execute(f"XA START '{xid.decode()}'")

        # 执行 SQL 操作(注意:UPDATE 语句不需要 FROM 子句)
        cursor1.execute("UPDATE accounts SET balance = balance - 100 WHERE user_id = 1")
        cursor2.execute("UPDATE accounts SET balance = balance + 100 WHERE user_id = 2")

        # 结束 XA 事务分支
        cursor1.execute(f"XA END '{xid.decode()}'")
        cursor2.execute(f"XA END '{xid.decode()}'")

        # 第一阶段:预提交
        cursor1.execute(f"XA PREPARE '{xid.decode()}'")
        cursor2.execute(f"XA PREPARE '{xid.decode()}'")

        # 第二阶段:提交
        cursor1.execute(f"XA COMMIT '{xid.decode()}'")
        cursor2.execute(f"XA COMMIT '{xid.decode()}'")
        print("XA 事务提交成功")

    except Error as e:
        print(f"XA 事务失败: {e}")
        # 回滚:对已 PREPARE 的分支执行 XA ROLLBACK
        if conn1:
            cursor1.execute(f"XA ROLLBACK '{xid.decode()}'")
        if conn2:
            cursor2.execute(f"XA ROLLBACK '{xid.decode()}'")
    finally:
        if conn1:
            conn1.close()
        if conn2:
            conn2.close()

二、TCC 模式

原理:TCC 将事务拆分为 Try(预留资源)、Confirm(确认提交)、Cancel(回滚释放)三个阶段,由业务代码实现每个阶段的接口,配合全局事务协调器(如 DTM)完成两阶段提交。

示例(使用 DTM 框架,HTTP 接口方式):

python 复制代码
# 需要先部署 DTM 服务(https://github.com/dtm-labs/dtm)
from dtmcli import tcc
import requests

SVC = "http://localhost:8080"      # 业务服务地址
DTM_SERVER = "http://localhost:36789"

def tcc_transfer():
    # 发起 TCC 全局事务
    gid = tcc.tcc_global_transaction(DTM_SERVER, None, tcc_trans)
    print(f"TCC 事务成功,GID: {gid}")

def tcc_trans(t):
    req = {"amount": 100, "from_user": 1, "to_user": 2}
    # 注册转出和转入的 Try/Confirm/Cancel 接口
    t.call_branch(req,
                  f"{SVC}/api/TransOutTry",
                  f"{SVC}/api/TransOutConfirm",
                  f"{SVC}/api/TransOutCancel")
    t.call_branch(req,
                  f"{SVC}/api/TransInTry",
                  f"{SVC}/api/TransInConfirm",
                  f"{SVC}/api/TransInCancel")

# 对应的业务接口实现(示例使用 Flask)
"""
@app.route('/api/TransOutTry', methods=['POST'])
def trans_out_try():
    data = request.json
    # 检查余额并冻结金额(在用户表中增加冻结字段)
    cursor.execute("UPDATE accounts SET frozen = frozen + %s WHERE user_id = %s AND balance - frozen >= %s",
                   (data['amount'], data['from_user'], data['amount']))
    return {'status': 'ok'}

@app.route('/api/TransOutConfirm', methods=['POST'])
def trans_out_confirm():
    data = request.json
    # 扣减余额并解冻
    cursor.execute("UPDATE accounts SET balance = balance - %s, frozen = frozen - %s WHERE user_id = %s",
                   (data['amount'], data['amount'], data['from_user']))
    return {'status': 'ok'}

@app.route('/api/TransOutCancel', methods=['POST'])
def trans_out_cancel():
    data = request.json
    # 解冻金额
    cursor.execute("UPDATE accounts SET frozen = frozen - %s WHERE user_id = %s",
                   (data['amount'], data['from_user']))
    return {'status': 'ok'}
"""

三、Saga 模式

原理:将一个长事务拆分为多个本地事务,每个事务都有对应的补偿操作。正向操作依次执行,若某个失败则逆序执行补偿。

示例(使用 DTM):

python 复制代码
from dtmcli import saga

SVC = "http://localhost:8080"
DTM_SERVER = "http://localhost:36789"

def saga_order_flow():
    s = saga.Saga(DTM_SERVER, None)
    # 添加子事务(正向操作,补偿操作)
    s.add(f"{SVC}/api/CreateOrder", f"{SVC}/api/CancelOrder",
          {"order_id": "ORD001", "amount": 100})
    s.add(f"{SVC}/api/DeductStock", f"{SVC}/api/AddBackStock",
          {"product_id": "P001", "quantity": 1})
    s.add(f"{SVC}/api/DeductBalance", f"{SVC}/api/AddBackBalance",
          {"user_id": 1, "amount": 100})
    gid = s.submit()
    print(f"Saga 事务成功,GID: {gid}")

四、本地消息表(最终一致性)

原理:将跨库操作通过本地事务写入业务表和消息表,然后异步轮询消息表,将消息投递到下游服务,实现最终一致性。

修正后的完整示例(含正确的 SELECT FROM 子句):

python 复制代码
import mysql.connector
import json
import time

def create_order_with_message(order_id, user_id, product_id, quantity):
    conn = None
    try:
        conn = mysql.connector.connect(
            host='localhost', user='app_user', password='pass', database='order_db')
        cursor = conn.cursor()
        conn.start_transaction()

        # 1. 插入订单(订单表)
        cursor.execute(
            "INSERT INTO orders (order_id, user_id, product_id, quantity, status) "
            "VALUES (%s, %s, %s, %s, %s)",
            (order_id, user_id, product_id, quantity, 'created')
        )

        # 2. 插入消息(消息表)
        payload = json.dumps({
            "order_id": order_id,
            "product_id": product_id,
            "quantity": quantity
        })
        cursor.execute(
            "INSERT INTO outbox_message (message_id, topic, payload, status, next_retry_at) "
            "VALUES (%s, %s, %s, %s, %s)",
            (f"msg_{order_id}", "inventory_deduct", payload, 'pending', None)
        )

        conn.commit()
        print("订单及消息写入成功")
    except Exception as e:
        if conn:
            conn.rollback()
        print(f"失败: {e}")
    finally:
        if conn:
            conn.close()

def consume_messages():
    """独立轮询线程/进程"""
    conn = mysql.connector.connect(
        host='localhost', user='app_user', password='pass', database='order_db')
    cursor = conn.cursor()

    while True:
        # 查询待发送消息(注意 FROM 子句明确)
        cursor.execute(
            "SELECT message_id, topic, payload FROM outbox_message "
            "WHERE status = 'pending' AND (next_retry_at IS NULL OR next_retry_at <= NOW()) "
            "LIMIT 10 FOR UPDATE SKIP LOCKED"
        )
        messages = cursor.fetchall()
        for msg_id, topic, payload in messages:
            try:
                # 调用下游服务(如扣库存接口)
                send_to_downstream(topic, json.loads(payload))
                # 更新消息状态为已发送
                cursor.execute(
                    "UPDATE outbox_message SET status = 'sent' WHERE message_id = %s",
                    (msg_id,)
                )
                conn.commit()
            except Exception:
                # 重试:递增重试次数,计算下次重试时间
                cursor.execute(
                    "UPDATE outbox_message SET status = 'pending', retry_count = retry_count + 1, "
                    "next_retry_at = DATE_ADD(NOW(), INTERVAL POW(2, retry_count) SECOND) "
                    "WHERE message_id = %s",
                    (msg_id,)
                )
                conn.commit()
        time.sleep(5)

def send_to_downstream(topic, payload):
    if topic == "inventory_deduct":
        print(f"调用库存服务扣减: {payload}")
        # 实际 HTTP 请求略
    else:
        raise ValueError(f"Unknown topic: {topic}")

五、Seata AT 模式(简述)

Seata是阿里巴巴开源的分布式事务解决方案,支持AT、TCC、Saga、XA四种事务模式。其中AT模式是Seata独创的、对业务无侵入的事务模式。它在二阶段提交的基础上,采用本地undo log记录数据修改前后的状态,一阶段执行完后可立即释放锁和连接资源,吞吐量比XA模式更高。用户在接入AT模式时,只需配置好数据源,事务提交/回滚流程均由Seata自动完成,对业务代码几乎零侵入。

AT模式要求所有SQL必须走代理,默认全局隔离级别为读未提交(Read Uncommitted);若需全局读已提交,需使用SELECT FOR UPDATE语句。

但该模式目前仅官方 Java 客户端成熟,Python 生态暂无正式实现。Python 项目可考虑:

  • 使用 dtm框架(支持 TCC/Saga/XA/事务消息),跨语言友好。
  • 核心事务部分用 Java 实现,Python 通过 RPC 参与。

六、方案选型速查表

方案 一致性 性能 开发成本 典型场景
XA 强一致 已废弃,不推荐
TCC 强一致 支付、秒杀等短事务高并发场景
Saga 最终一致 长流程、多参与方(如订票)
本地消息表 最终一致 异步解耦,如订单→扣库存
Seata AT 强一致 低(Java) Java 微服务,低侵入需求

所有示例中的 SQL 语句(INSERT、UPDATE、SELECT)均包含完整的表名和 FROM 子句,您可以直接在 MySQL 中执行建表语句后测试。

如需完整的建表 DDL 或某个方案的详细部署步骤,请告知。

相关推荐
小张同学a.1 分钟前
zabbix企业级监控平台3——服务监控与API实战
linux·运维·mysql·nginx·tomcat·zabbix
九皇叔叔11 分钟前
RHEL9 安装 MySQL 8.4.11.LTS 版本
android·mysql·adb
海兰28 分钟前
【数据库】tdsql(MySQL )的事务隔离级别
android·数据库·mysql
灵析表格1 小时前
Excel连接MySQL的函数化革命:灵析表格MySQL函数族业务应用分析
数据库·mysql·adb·excel·wps·灵析表格·excel公式盒子
todoitbo1 小时前
把发票台账接进 Codex:KingbaseES MCP 的一次只读风险排查实践
数据库·oracle·codex·kingbasees·mcp
Navicat中国1 小时前
使用 Navicat 轻松生成数据库测试数据
数据库·数据
ruleslol1 小时前
MySQL: Undo日志与MVCC多版本并发控制
mysql
zyplayer-doc1 小时前
核心制度不该被随手修改:用zyplayer-doc锁定文档和全部子文档
大数据·数据库·人工智能·笔记·pdf·ocr
一路向北North2 小时前
Spring AI(11) :ChatPDF-向量数据库、PDF处理、向量写入和向量搜索
数据库·人工智能·spring
暗暗别做白日梦2 小时前
CompletableFuture 并发统计
网络·数据库·oracle