河内机器人 环境:Docker Swarm 集群 + MariaDB 10.4,业务使用 XA 分布式事务
作者:admin | 分类:河内机器人 | 浏览:2 | 日期:2026年09月20日这个话题指向的是今天(2026-09-20 15:16)博客园"未来AI笔记"最新发布的生产故障案例,结合 CSDN
上同类的 MySQL Docker XA 事务锁排查指南和 MariaDB 官方文档,以下是完整的故障排查体系。
[citation:18.1][citation:18.4][citation:18.47]
---
一、故障场景与现象
环境:Docker Swarm 集群 + MariaDB 10.4,业务使用 XA 分布式事务
典型故障链路:
Docker Swarm 节点异常 → MariaDB 容器被 Swarm 重新调度
→ 容器重启 → MariaDB 启动时发现残留 XA PREPARED 事务
→ 启动卡住/失败 → 业务无法连接数据库
现象:[citation:18.1][citation:18.6]
MariaDB 容器启动失败,或启动后无法正常服务
错误日志出现:Found N prepared XA transactions
业务日志报锁超时、连接被拒绝
docker service ps 显示服务不断重启(CrashLoopBackOff)
XA RECOVER 可见残留事务,但 XA COMMIT 返回 XAER_NOTA
---
二、XA 悬停事务的根因
2.1 两阶段提交(2PC)的断裂
XA 事务的核心流程:
XA START → 业务操作 → XA END → XA PREPARE → XA COMMIT
当应用在 XA PREPARE 之后、XA COMMIT 之前崩溃(或协调者失联),事务进入 PREPARED 状态。[citation:18.7]
2.2 为什么 Docker Swarm 下更易触发
| 因素 | 在 Swarm 中的表现 |
|------|-----------------|
| 节点调度 | Swarm 将 MariaDB 容器调度到新节点,旧节点的 XA 事务状态未同步 |
| 数据卷漂移 | 如果 volume 未正确挂载,新容器读不到旧容器的 tc.log |
| 健康检查 | Swarm 健康检查失败后会强制重启容器,中断正在进行的 XA 事务 |
| 网络抖动 | Overlay 网络不稳定会导致协调者与 MariaDB 的连接超时 |
2.3 MariaDB 特有的 tc.log
与 MySQL 不同,MariaDB 使用 事务协调器日志(tc.log) 来持久化 XA 事务的 PREPARED 状态。重
启时,MariaDB 会扫描 tc.log 恢复未完成的 XA 事务。[citation:18.5][citation:18.50]
如果 Docker 数据卷在重新调度时损坏或丢失,tc.log 读不出来——MariaDB 就无法自动恢复,导致启动卡住。
---
三、分层排查步骤
第一层:确认 Swarm 集群与容器状态
检查 Swarm 节点状态
docker node ls
排查 MariaDB 服务的运行状态
docker service ps <mariadbservicename>
查看容器日志,寻找关键错误
docker logs <container_id> --tail 200 | grep -i "prepared\|XA\|tc.log"
检查数据卷挂载情况
docker inspect <container_id> | jq '.[].Mounts'
预期发现:日志中出现 Found N prepared XA transactions,容器反复 Crash。
第二层:进入 MariaDB 诊断 XA 事务
如果能登录 MariaDB(即使只读),执行:
-- 1. 查看所有处于 PREPARED 状态的 XA 事务
XA RECOVER;
-- 2. 查看当前活动事务及运行时长
SELECT trxid, trxstate, trx_started,
TIMESTAMPDIFF(SECOND, trxstarted, NOW()) AS ageseconds
FROM informationschema.innodbtrx
WHERE trx_state = 'RUNNING';
-- 3. 查看锁等待情况
SELECT * FROM performanceschema.datalocks;
典型输出:[citation:18.1]
+----------+-----------+---------------------+--------------+
| trxid | trxstate | trxstarted | ageseconds |
+----------+-----------+---------------------+--------------+
| 30330271 | RUNNING | 2026-09-18 15:56:12 | 64362 |
+----------+-----------+---------------------+--------------+
事务运行了 17+ 小时,显然已经是僵尸事务。
第三层:尝试正常恢复(温和手段)
-- 1. 直接提交(如果业务协调者可确认已提交)
XA COMMIT '<xid_string>';
-- 2. 或回滚(如果业务确认需取消)
XA ROLLBACK '<xid_string>';
⚠️ 如果返回 XAER_NOTA: Unknown XID——说明 XID 格式异常或 tc.log 已损坏,需要走激进恢复。
第四层:激进恢复(tc.log 损坏时)
方案 A:innodbforcerecovery(无效时跳过)
设置 innodbforcerecovery=1 启动 MariaDB,然后执行 XA RECOVER 手动清理。但如前文所证,恢复模式无法清除 PREPARED 状态的 XA 事务。[citation:18.1]
方案 B:tc.log 截断(最后手段)
确认数据目录位置
docker inspect <container> | grep Source
停止容器
docker service scale <mariadb_service>=0
找到 tc.log 并备份
sudo cp /path/to/data/tc.log /path/to/data/tc.log.bak
清空 tc.log(MariaDB 重启会重建它)
sudo truncate -s 0 /path/to/data/tc.log
重启服务
docker service scale <mariadb_service>=1
⚠️ 前提:必须确认业务已经通过 Saga 补偿或业务兜底完成了数据一致性。截断 tc.log 相当于"告诉 MariaDB 这些 XA 事务就当没发生过"——如果业务侧实际上没有完成提交,会留下数据不一致。
方案 C:完整备份恢复(最安全)
以跳过锁表方式备份
docker exec <db> mysqldump -uroot -p<pass> \
--all-databases --skip-lock-tables --set-gtid-purged=OFF \
/tmp/full_backup.sql
彻底清理数据目录
docker service scale db=0
sudo rm -rf /path/to/data/*
以干净配置启动新容器
docker service scale db=1
恢复数据
docker exec -i <db> mysql -uroot -p<pass> < /tmp/full_backup.sql
---
四、生产预防措施
4.1 应用层:XA 事务监控
// 定时检查长时间运行的 XA 事务
public class XATransactionMonitor : IHostedService
{
private Timer _timer;
public Task StartAsync(CancellationToken ct)
{
_timer = new Timer(CheckXATransactions, null,
TimeSpan.Zero, TimeSpan.FromMinutes(30));
return Task.CompletedTask;
}
private void CheckXATransactions(object state)
{
using var conn = new MySqlConnection(connectionString);
conn.Open();
// 查找运行超过 300 秒的事务
var cmd = new MySqlCommand(@"
SELECT trxid, TIMESTAMPDIFF(SECOND, trxstarted, NOW())
FROM informationschema.innodbtrx
WHERE trx_state = 'RUNNING'
AND TIMESTAMPDIFF(SECOND, trx_started, NOW()) > 300", conn);
// ... 告警逻辑
}
}
4.2 Docker Swarm 层面
docker-compose.yml 关键配置
services:
mariadb:
image: mariadb:10.4
# ✅ 避免健康检查过于激进
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 30s # 不要用默认的 5s
timeout: 10s
retries: 3
start_period: 60s # 给启动留足缓冲时间
# ✅ 数据卷必须挂载到持久化存储
volumes:
mariadb_data:/var/lib/mysql
# ✅ 部署约束:确保数据库跑在有持久化卷的节点上
deploy:
placement:
constraints:
node.labels.storage == ssd
# ✅ 双次停止宽限期,让正在执行的事务有机会完成
stopgraceperiod: 120s
4.3 架构层面:避免 XA 的替代方案
| 方案 | 原理 | 适用场景 |
|------|------|---------|
| Saga 模式 | 拆成多个本地事务 + 补偿操作 | 长事务、跨服务调用 |
| TCC(Try-Confirm/Cancel) | 预留资源 → 确认/取消 | 库存、账户扣减 |
| 最终一致性 + 消息表 | 本地事务 + 可靠消息 | 大多数业务场景可接受 |
五、一句话总结
MariaDB XA 悬停事务在 Docker Swarm 下的本质,是 2PC 协议在容器编排环境中的"协调者失忆"问题——容器被重新调度时,PREPARED 状态的 XA 事务丢失了协调者上下文,而 tc.log 的存在又阻止了数据库侧自动回滚。 [citation:18.7] 恢复的核心是确认业务侧一致性状态后,手动清理 tc.log 或走完整备份恢复;预防的核心是应用层增加 XA 事务超时监控 + Swarm 配置合理的健康检查与停止宽限期。