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 或某个方案的详细部署步骤,请告知。

相关推荐
m0_676544382 小时前
Golang怎么解决nil pointer错误_Golang如何排查和修复空指针引用崩溃【避坑】
jvm·数据库·python
风兮雨露2 小时前
Windows 部署Redis免安装版以及客户端
数据库·windows·redis
与数据交流的路上2 小时前
mysql-通过binlog分析大事务
数据库·mysql
2301_777599372 小时前
如何优化宝塔面板的服务器内存使用_调整MySQL内存占用
jvm·数据库·python
gushinghsjj2 小时前
数据管理工具怎么选?数据管理工具应具备什么?
数据库
瀚高PG实验室2 小时前
PostgreSQL 优化器统计信息可能会在视图、分区或子表中暴露采样数据HGVE-2025-E006
数据库·postgresql·瀚高数据库
NoSi EFUL2 小时前
学生成绩管理系统(MySQL)
android·数据库·mysql
Yeats_Liao2 小时前
Trae 配置 MySQL MCP 指南
数据库·mysql
java干货2 小时前
Redis 分布式限流的四大算法与终极形态
数据库·redis·分布式