Composer无法直接提供开箱即用的分布式事务包,因该能力需与服务拓扑、网络延迟、补偿逻辑强耦合;TP6/Laravel中真正可复用的是think-queue、think-cache等底层组件及dtm-client等通信SDK,而非自治事务包。

Composer 不能直接提供分布式事务组件
TP6 或 Laravel 项目里,composer require 不可能装出一个“开箱即用”的分布式事务包。这不是 Composer 的能力边界问题,而是分布式事务本身无法被通用化封装——它必须和你的服务拓扑、网络延迟、失败重试策略、补偿逻辑强耦合。你搜到的 thinkphp/distributed-transaction、tp-tcc 等包,99% 是命名误导的玩具实现,或硬改 Laravel 代码塞进 TP autoload 的混搭品,Class not found 或 Method not exists 是常态。
真正可落地的 Composer 依赖组合
分布式事务一致性靠的是组件拼装,不是单个包。TP6 项目中能稳定复用的,其实是官方生态里已验证的底层能力:
-
topthink/think-queue:用于消息驱动的最终一致性,配合本地消息表使用 -
topthink/think-cache:支撑 TCC 的 Try 阶段状态缓存(如冻结库存 TTL)、Saga 补偿动作幂等控制 -
dtm/dtm-client:PHP 客户端,对接 DTM 协调器(需单独部署),支持 TCC/Saga/XA 模式,composer require dtm/dtm-client可行,但协调服务不在 Composer 范畴内 -
hyperf/dtmsaga(Hyperf 生态):若用 Hyperf,该包提供 Saga 模式编排能力,但 TP6 项目无法直接复用
注意:dtm/dtm-client 仅是通信层 SDK,不包含事务协调器;它依赖外部 DTM 服务运行,不是“装完就能用”的自治组件。
跨库事务写法必须放弃 Db::transaction() 包裹
TP6 的 Db::transaction() 只作用于单个数据库连接实例。跨库操作(比如主库写订单 + 从库写日志)强行套用会静默失败,尤其在 PostgreSQL 或 SQL Server 下——因为不同库对应不同连接,事务上下文不共享。
正确做法是分层拆解:
- 同库多表:用
Db::transaction()包住,确保本地原子性 - 跨库写入:拆成“主库事务 + 异步消息”,消息体含业务 ID 和关键参数,由消费者更新从库;失败走死信队列
- 状态兜底:主库业务表加
_sync_status字段,定时任务扫描不一致记录并触发修复
别试图用 Db::startTrans() + Db::commit() 在不同模型间接力——这在 MySQL 下可能侥幸成功,但在其他数据库上大概率失效且无报错。
TCC 接口设计最容易忽略的三个细节
TCC 不是把业务函数改个名就叫分布式事务。Try/Confirm/Cancel 三接口必须满足特定契约,否则上线后就会出现悬挂事务或重复补偿:
-
Try接口必须落库(如写user_frozen_balance记录),不能只存在内存或 Redis 中;否则服务重启后无法恢复状态 -
Confirm和Cancel必须幂等,且不能校验 Try 阶段状态——因为冻结可能已被超时清理,校验反而破坏幂等性 - 全局事务 ID 和各分支 Try 结果必须持久化到一张
global_transactions表,不能只靠内存或临时缓存
这些逻辑没法靠 Composer 包自动注入,得你亲手写进业务代码里。所谓“组件”,只是帮你省掉 HTTP 通信、序列化、重试封装这些样板代码,核心状态机永远要自己定义。


















