事务里一查就慢的本质是未命中索引的查询在事务中被锁放大;wiredtiger下collscan会升级为集合级争用,导致锁排队、millis飙升、totaldocsexamined远超nreturned。

事务里一查就慢,本质是索引没用上
事务本身不拖慢 MongoDB,真正卡住的是未命中索引的查询在事务生命周期内被锁放大。WiredTiger 对文档加读/写锁时,若底层执行计划是 COLLSCAN,就会升级为集合级争用——多个并发事务同时扫描同一集合,锁排队、millis飙升、totalDocsExamined远大于 nReturned 就是典型信号。
必须用 db.collection.explain("executionStats").find(...) 检查执行统计:
-
executionStats.executionStages.stage === "COLLSCAN"→ 当前查询完全没走索引 -
totalDocsExamined / nReturned > 10→ 即使走了索引,也可能因字段顺序错或未覆盖导致低效扫描 - 避免在事务中用
$where、无前缀正则(如/abc$/)、$text等无法利用 B-tree 索引的操作
高频更新同一文档或范围,锁冲突直接拉满
哪怕有索引,如果多个事务反复修改同一个文档(比如计数器、状态字段),或批量更新重叠的时间范围(如 createdAt: { $gte: ..., $lt: ... }),WiredTiger 的文档级锁会退化为“逻辑热点”。这不是锁粒度问题,而是访问模式制造了竞争瓶颈。
关键指标要盯紧:
-
db.serverStatus().metrics.operation.write.conflicts—— 每秒写冲突 > 5 次就得干预 -
wt_txn_update_conflict计数持续上升 → 事务重试率高,CPU 白耗在重试循环里 - 改用
findOneAndUpdate替代“先find再update”的读-改-写流程,减少锁持有窗口
跨分片事务或连接池配置不当,雪上加霜
分片集群下,事务必须限定在单一分片内,否则会强制升级为全局协调锁,延迟陡增。而连接池配置错误会让问题更隐蔽:
- 默认
maxPoolSize: 100看似够大,但若事务未及时commit或rollback,连接和锁资源就被长期占用 - Node.js 驱动建议设
maxPoolSize: 50+minPoolSize: 10+maxIdleTimeMS: 60000,避免空闲连接堆积又突然失效 - 所有事务必须显式设置
maxTimeMS,防止长事务无限期阻塞其他操作
别只盯着事务,先确认是不是它在跑
很多所谓“事务拖垮TPS”的问题,实际是长事务没结束,或者压根不是事务在跑。用 db.currentOp({ "secs_running": { "$gt": 5 } }) 查真实卡住的操作,重点关注 ns 和 query 字段;再查 system.profile 中 docsExamined 动辄几万的慢查询——这些才是 CPU 高、TPS 下降的真正推手。
索引优化、访问模式收敛、连接与超时控制,这三件事没做完,调事务隔离级别或重写业务逻辑都只是绕开问题。











