事务必须显式开启,即调用db.startTransaction()获取transaction实例,所有操作须通过该实例执行;跨集合支持因云厂商而异,阿里云不支持且单事务限10次操作;防超卖需在事务内原子化“查+扣”,禁用where().update();超时需延长云函数timeout并显式rollback;大批量场景应分批+幂等设计。

事务必须显式开启,否则根本没有原子性保障
uniCloud 的事务不是自动生效的,db.startTransaction() 不调用,就等于没事务。很多人写了一堆 update() 和 remove(),以为只要连着写就是“事务”,结果库存扣减成功了但订单没生成,或者反过来——失败时前几步根本不会回滚。
常见错误现象:前端点击下单后,数据库里订单多了一条,但商品库存没变;或库存扣了,订单却没创建。这不是并发问题,是压根没进事务。
- 必须在云函数里调用
db.startTransaction()获取 transaction 实例 - 所有增删改操作都得通过该实例的
collection().xxx()调用,不能混用普通db.collection() - 腾讯云和阿里云对事务支持差异大,务必先确认
provider配置('tencent'或'aliyun')
超卖场景下,事务内只能用 doc(id).update() 做库存扣减
防超卖的核心是「查+扣」必须原子化,且不能依赖条件更新——where({ stock: db.command.gt(0) }).update() 看似安全,实则在高并发下会因查询与更新之间的时间差导致超扣。
正确做法是:先用 doc(id).field('stock').get() 读出当前库存,判断是否足够;若足够,再用 doc(id).update() 扣减。这两步必须包在同一个事务里。
-
doc(id).update()走索引,快且可控;而where().update()是全表扫描,数据量一过千就容易超时或被限频 - 阿里云事务不支持跨集合,所以「查商品 → 扣库存 → 写订单」必须拆成两个事务,或把订单信息冗余到商品文档里(不推荐)
- 腾讯云允许跨集合,但单事务最多 10 次操作,所以「查商品 + 扣库存 + 写订单 + 写日志」要精算次数,避免触发
Transaction operation limit exceeded
事务超时不是网络慢,而是云函数硬性截止时间
uniCloud 默认云函数超时是 5 秒,而事务的 commit() 或 rollback() 必须在这个窗口内完成。一旦某次 doc(id).update() 因索引缺失、网络抖动卡住,整个事务就直接中断,且不会自动重试。
最容易被忽略的是:没在 catch 里调用 transaction.rollback()。未提交的事务可能长期挂起,占用数据库连接资源,后续请求全被阻塞。
- 处理 >20 条记录时,务必在云函数配置中把
timeout提到 15–30 秒 - 事务内禁止 HTTP 请求、文件读写、复杂计算——这些操作应移到事务外
-
catch块里必须显式调用await transaction.rollback(),不能只抛错 - 前端需配合状态同步:事务失败时,要清除本地已提交的 UI 状态(比如按钮 loading 态、临时缓存的订单号)
大批量抢购不能强依赖单事务,得靠分批+幂等设计
哪怕你把所有逻辑都塞进一个事务,也扛不住万人同时点「立即抢购」。单事务性能瓶颈明显,且阿里云限制更死(单集合、≤10 次操作),真要支撑大流量,必须放弃「一个事务兜底」的幻想。
真实可行的路径是:用乐观锁(version 字段)做前置校验 + 分批事务 + 异步落库补偿。比如 1000 个用户抢 100 件商品,可按每 20 人一批,每批启一个独立事务,失败批次走降级流程(如进队列重试)。
- 别在事务里做
_.in(idList)批量删/改,性能差且易超限;优先转为循环doc(id).update() - ID 列表一次别超过 50 个,实测成功率接近 100%;超了就拆批
- 事务只是兜底手段,核心防超卖逻辑还得落在数据库层面:给库存字段加唯一约束、用
db.command.inc()原子递减、配合db.command.gt()条件校验











