PHP MongoDB事务仅支持副本集或分片集群,单节点不支持;必须显式传入session、配置readConcern/writeConcern,并捕获超时与异常以确保一致性。

PHP MongoDB事务必须在副本集或分片集群中启用
单节点 MongoDB 实例不支持事务,startSession() 能调用成功,但 startTransaction() 会直接抛出 MongoDB\Driver\Exception\RuntimeException:“Transactions are not supported on this topology”。确认环境是否满足要求,比写代码更关键。
检查方式很简单:连接后执行 db.adminCommand({ replSetGetStatus: 1 })(通过 shell 或 executeCommand()),有 members 数组且 myState 为 1 才算主节点;或者直接看连接字符串是否含多个 host + ?replicaSet=xxx。
- 本地开发常用
docker run -d -p 27017:27017 mongo:6 --replSet rs0,再进容器执行rs.initiate() - Laravel 用户注意:
mongodb/mongodb包本身不校验拓扑,靠底层 libmongoc 判断,错误常延迟到commitTransaction()才暴露 - 云服务如 MongoDB Atlas 默认启用副本集,但 Serverless 实例明确不支持事务——别被控制台“兼容 MongoDB 协议”误导
session 对象必须显式传入每个操作,不能依赖全局上下文
PHP 的 MongoDB 驱动不维护线程/请求级的隐式 session,collection->insertOne() 这类调用默认不在事务内。必须把 $session 作为选项传进去,漏掉任意一次,该操作就脱离事务边界,造成数据不一致。
常见错误写法:
立即学习“PHP免费学习笔记(深入)”;
$session = $client->startSession(); $session->startTransaction(); $collection->insertOne(['x' => 1]); // ❌ 没传 $session,已提交 $collection->insertOne(['x' => 2], ['session' => $session]); // ✅ 在事务中
- 所有读写操作(
find(),updateOne(),deleteMany()等)都需显式加['session' => $session] - 事务内读操作默认使用
snapshot级别隔离,但若要跨集合强一致性,必须确保所有集合在同一分片键下,否则可能读到部分提交状态 - 不要复用
$session处理多个并发请求——它不是线程安全的,PHP-FPM 下每个请求应新建 session
事务超时与自动 abort 的边界条件必须手动捕获
MongoDB 服务端默认 60 秒事务超时,但 PHP 驱动不会主动抛异常;它可能卡在 commitTransaction() 直到 socket timeout(通常更长),或静默失败。真正危险的是:网络中断后 commitTransaction() 抛 ConnectionTimeoutException,但服务端事务其实已提交了一半。
- 务必用
try/catch包裹整个事务块,并在catch中调用$session->abortTransaction()(即使不确定是否还在活跃状态) - 设置客户端超时:
$client = new \MongoDB\Client($uri, ['socketTimeoutMS' => 10000]),让失败更快暴露 - 避免在事务中做耗时操作(如 HTTP 请求、文件读写),它们无法回滚,且会拖垮整个事务生命周期
- 如果业务允许,优先用「无事务最终一致性」方案:例如先写消息队列,再由消费者更新 MongoDB,比硬扛事务复杂度更可靠
readConcern 和 writeConcern 不是可选配置,而是事务语义的组成部分
不指定 readConcern,事务内读可能看到其他未提交事务的数据(取决于存储引擎);不指定 writeConcern,commitTransaction() 返回成功不代表数据已落盘,主节点宕机可能导致回滚。
生产环境应强制设置:
$options = [
'readConcern' => new \MongoDB\Driver\ReadConcern('majority'),
'writeConcern' => new \MongoDB\Driver\WriteConcern(2, 1000),
];
$session->startTransaction($options);
-
readConcern: "majority"保证读到已提交至大多数节点的数据,防止脏读 -
writeConcern: { w: 2, wtimeout: 1000 }要求至少两个节点确认写入,超时 1 秒即报错,避免无限等待 - 注意:若副本集只有 1 个节点(比如开发环境),
w: 2会永远失败,需按环境动态切换配置 - PHP 驱动对
ReadConcern和WriteConcern的构造函数参数敏感,传字符串或数组都可能报错,必须用对应类实例
事务不是银弹。当遇到高并发写同一条文档、或需要跨分片原子操作时,驱动层能做的非常有限——这时候得回到数据建模本身,比如把频繁争抢的计数器拆成多条记录再聚合,比死磕事务更实际。



















