事务慢的根源在于事务内find/update/insert等操作本身或锁等待、网络延迟,而非commit慢;Profiler默认不记录commitTransaction,需通过lsid和txnNumber字段定位事务内慢操作,并结合db.currentOp()、explain等综合分析。

事务慢不是commit慢,得先确认是哪一步卡住
Profiler 默认不记录 commitTransaction 或 abortTransaction,所以看到 system.profile 里没它们,不代表事务没执行完——这是正常现象。真正拖慢事务的,是里面那些 find、update、insert 操作本身,或是锁等待、网络延迟。
先用 db.currentOp() 看实时状态:
-
secs_running高且waitingForLock: false→ 操作本身执行重(比如没走索引、聚合太深) -
waitingForLock: true→ 被别的事务或写操作堵住了,得查谁在 hold 锁 -
secs_running低但客户端等很久 → 问题可能出在驱动层(如批量提交策略)、连接池、或事务外的序列化/反序列化开销
开启Profiler并过滤出事务内操作
事务内所有命令都会带 lsid 和 txnNumber 字段,这是唯一可靠标识。别指望靠 command.name === "commitTransaction" 找——它默认不记。
启用方式(任选其一):
- 配置文件中加:
operationProfiling: { mode: "slowOp", slowOpThresholdMs: 100 } - 运行时执行:
db.setProfilingLevel(1, { slowms: 100 })
之后查日志时,必须按这两个字段聚合:
db.system.profile.find({
"lsid": { "$exists": true },
"txnNumber": { "$exists": true },
"millis": { "$gt": 100 }
}).sort({ "ts": -1 })
这样才不会把单条慢查询和事务混在一起误判。
从Profiler日志里看索引是否生效
关键不是看 millis 多大,而是看扫描和返回的比值:
-
nScannedObjects远大于nReturned→ 很可能没命中索引,触发了COLLSCAN -
keysExamined高但nScannedObjects低 → 索引范围扫得宽,但文档少,可能是复合索引顺序不对,或查询条件没用上前缀 -
docsExamined对于update/delete明显高于预期 → 条件缺失或索引失效,得用explain("executionStats")复现验证
例如这条日志:"planSummary": "COLLSCAN", "nScannedObjects": 124800, "nReturned": 1,基本可以断定该 find 没走索引。
事务瓶颈常被忽略的三个点
很多人查完 system.profile 就停了,但容易漏掉这些:
- 事务跨多个分片时,
lsid+txnNumber在每个分片上独立生成,不能直接全局聚合——得去各分片单独查system.profile - 驱动默认开启
maxPoolSize限制,高并发事务下连接池耗尽,会阻塞在获取连接阶段,这不体现在system.profile里 -
writeConcern设为{ w: "majority" }时,commit 等待多数节点落盘,网络延迟会被算进客户端感知延迟,但Profiler只记服务端执行时间
事务慢从来不是单点问题,得把 currentOp、system.profile、驱动配置、网络拓扑这几块拼起来看。











