MySQL 分布式事务主流方案对比与实战(修正版)
一、XA 协议(两阶段提交)
原理 :XA 规范定义了事务管理器(TM)与资源管理器(RM)的交互接口。MySQL 通过 XA START、XA END、XA PREPARE、XA 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 或某个方案的详细部署步骤,请告知。