事务内写入卡顿主因不是并发数低,而是事务粒度过大、锁范围过宽及快照生命周期过长;当active与max接近且ticket.queued持续>0时,说明写票证耗尽,调高concurrentTransactions.write无效,须重构事务逻辑。

事务内写入卡顿,是不是并发数设低了?
不是简单调高 concurrentTransactions.write 就能解决。WiredTiger 的写票证(write ticket)机制会动态限制同时进行的写事务数量,硬调高参数反而可能触发更频繁的等待或内存压力。真实瓶颈往往在事务粒度、锁范围和快照生命周期上。
关键判断点:db.serverStatus().wiredTiger.transaction 中的 active 和 max 值接近,且 ticket.queued 持续 > 0,说明写票证已耗尽,此时调参无效,得重构事务逻辑。
- 单个事务内避免跨多个分片写入(尤其在
sharded集群中),否则会升级为两阶段提交,延迟陡增 - 事务内不要执行
count()、distinct()等全集合扫描操作,它们会延长快照持有时间 - 用
db.getSiblingDB("admin").runCommand({setParameter:1, wiredTigerEngineConfigString:"eviction_target=75"})调整淘汰目标,缓解缓存压力(仅限紧急场景)
为什么小事务拆分后反而更慢?
拆分本身没错,但若每个子事务都包含重复的查询前置动作(比如反复查同一份用户配置),就会放大网络往返和锁竞争。WiredTiger 对短事务的开销敏感,尤其是频繁创建/销毁快照带来的 CPU 消耗。
典型错误模式:for (let i = 0; i —— 这种循环开启事务,比批量写入慢 3–5 倍。
- 把可并行的写操作尽量放在同一个事务内(如批量更新同一批文档),减少事务启动/提交开销
- 对非强一致性场景,改用带
w: "majority"的单文档写入 + 应用层补偿,绕过事务框架 - 使用
bulkWrite()替代事务内多次updateOne(),即使不在事务中,也能合并 IO
写关注(write concern)怎么配才不拖慢事务?
w: "majority" 是默认值,但在高吞吐写入时,它会让主节点等多数副本落盘才返回,显著拉长事务生命周期。而 w: 1 虽快,但可能丢数据;w: 0 更危险,事务回滚时无法保证日志一致性。
安全又高效的折中方案是:在事务外用 w: "majority",事务内降级为 w: 1,并通过应用层控制事务边界(例如只在关键状态变更时启用事务)。
- 事务内禁止设置
j: true—— 它强制日志刷盘,与事务本身的 WAL 日志重复,徒增 I/O - 检查
storage.journal.enabled是否开启:关闭 journal 会加速写入,但必须配合w: "majority"使用,否则故障恢复不可靠 - SSD 环境下,可将
storage.wiredTiger.engineConfig.journalCompressor: "zlib"改为"snappy",降低压缩 CPU 开销
索引过多导致事务写入变慢,删哪些?
事务内每条写操作都要同步更新所有匹配索引,而 WiredTiger 的索引页是独立缓存的。当事务修改 1000 条文档,且集合有 8 个索引时,实际写入量可能是数据本身的 8 倍以上。
重点排查:db.collection.getIndexes() 中未被 explain("executionStats") 覆盖的索引,尤其是 partial 或 sparse 索引——它们在事务中仍会被遍历判断是否生效。
- 删除字段值恒为
null或固定字符串的索引(如{status: 1}且 95% 是"active") - 合并重叠索引:存在
{a: 1, b: 1}和{a: 1, b: 1, c: 1}时,保留后者即可 - 对时间字段建 TTL 索引时,避免在事务内频繁插入带
expireAt的文档,TTL 后台线程会与事务争抢同一文档锁

















