置顶

河内机器人 环境: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 配置合理的健康检查与停止宽限期。