db.setprofilinglevel(2) 会掩盖死锁信号且加剧锁竞争,应禁用;排查死锁须组合使用 db.currentop()(筛选 waitingforlock 和长事务)与错误日志(聚焦 writeconflict 等关键词),并注意时间戳对齐和索引冲突分析。

db.setProfilingLevel(2) 会掩盖死锁信号,别开
开启 db.setProfilingLevel(2) 确实能记录所有操作,但它不记录锁等待链、事务阻塞关系或资源争用上下文。死锁发生时,MongoDB 事务会直接抛出 TransactionAborted 或 WriteConflict 异常并回滚,而 profiling 日志里只留下一条“已执行但失败”的事务日志,看不出谁在等谁、等了多久、卡在哪条语句。更麻烦的是,profiling 开启后性能损耗明显,尤其高并发写场景下,日志写入本身就会加剧锁竞争——相当于想查堵车原因,却先往路口加了一排路障。
真正有用的日志来源只有两个:currentOp + error log
排查事务死锁必须组合使用:db.currentOp() 实时抓活锁现场,/var/log/mongodb/mongod.log(或你配置的 logPath)查历史异常上下文。重点不是“有没有慢查询”,而是“有没有长时间卡住的事务”和“有没有重复出现的 WriteConflict”。
-
db.currentOp({ "waitingForLock": true }):直接筛出正在等待锁的操作,关注waitTime字段,超过 5000ms(5秒)基本可判定为异常阻塞 -
db.currentOp({ "secs_running": { "$gt": 30 } }):找出运行超 30 秒的长事务,这类事务极易成为死锁中的“持锁方” - 错误日志中搜索关键词:
WriteConflict、TransactionAborted、InterruptedDueToReplStateChange(后者常是主从切换引发的伪死锁) - 注意时间戳对齐:
db.currentOp()返回的secs_running是相对当前时刻的,而日志里的时间是绝对时间,需换算比对
error log 里 WriteConflict 不等于死锁,但它是关键线索
MongoDB 官方明确说明:它不实现传统意义上的“死锁检测器”,而是靠乐观并发控制(OCC)在提交阶段检测写冲突。所以你不会在日志里看到“DEADLOCK DETECTED”这种字样,只会看到 WriteConflict —— 这表示两个事务修改了同一文档(或同一索引键范围),后提交者被强制回滚。高频 WriteConflict 就是死锁风险的 proxy 指标。
- 如果日志中
WriteConflict集中出现在某几个集合(如orders、accounts),优先检查这些集合的更新模式:是否多个服务/线程在无序地并发$inc同一字段? - 配合
db.currentOp()查看冲突事务的ns(命名空间)和query,确认是否都命中相同索引(比如都走{ userId: 1, status: 1 }) - 注意区分:单文档事务内多操作不会触发 WriteConflict;跨文档事务才可能,且冲突概率随事务内修改文档数上升而指数增长
profiling 可以开,但必须配 filter + 限等级
如果你真需要 profiling 辅助分析(比如定位哪类聚合拖慢了事务提交),请严格按以下方式启用,否则得不偿失:
- 只在问题时段临时开启:
db.setProfilingLevel(1, { slowms: 100 })(100ms 以上才记,避免淹没日志) - 务必加 filter:
db.setProfilingLevel(1, { slowms: 100, filter: { "secs_running": { "$gt": 5 } } }),聚焦长事务本身 - 绝不要设为 level 2,也别长期开着;查完立刻关:
db.setProfilingLevel(0) - profile collection 默认是
system.profile,确保它有独立的 TTL 索引(db.system.profile.createIndex({ "ts": 1 }, { expireAfterSeconds: 3600 })),防止日志膨胀拖垮元数据性能
真正卡住业务的,从来不是某条慢查询,而是事务间锁资源的循环依赖。盯紧 currentOp 的 waitingForLock 和错误日志的 WriteConflict 时间密度,比翻 profiling 日志有效十倍。











