MongoDB WriteConflict 排查思路

MongoDB WriteConflict 排查思路

业务中时长遇到 WriteConflict 错误,虽然业务上有重试最终没有影响。但是理清逻辑,确认 到底是哪些地方导致的还是非常重要。下面我们提供一下事后常见的排查路径,希望对大家有用。

在 MongoDB 中,事务冲突的详细信息通常会在服务器日志中记录,但记录的详细程度取决于MongoDB的日志级别设置。

怎么知道发生了 WriteConflict

通常有 2 个角度可以观察到,一个是 MongoDB 本身的日志、另外就是业务应用在通过 MongoDB 驱动执行操作时候的报错。

查询 MongoDB 日志

我们可以在 MongoDB 的日志中查找到事务冲突的信息。以下日志路径仅供参考,具体的路径要根据自己的配置文件进行调整。

bash 复制代码
   grep "WriteConflict" /var/log/mongodb/mongod.log
   grep "TransientTransactionError" /var/log/mongodb/mongod.log

如果希望增加日志详细度(如果需要更多信息)

  • 临时提高日志级别,特别是事务相关操作:
javascript 复制代码
db.setLogLevel(1, "command")
db.setLogLevel(1, "transaction")
  • 记录后别忘了恢复默认设置:
javascript 复制代码
db.setLogLevel(-1, "command")
db.setLogLevel(-1, "transaction")

tracing

如果你的业务有 tracing 系统,那么通常我们可以通过 tracing 获取到相关信息(业务应用记录的)。

日志中的信息限制

MongoDB 日志通常不会显示具体的冲突事务详情:

  • 不会直接标识哪两个事务发生了冲突,比如我们某一个操作出错了,他只会告诉你当前的操作发生了事务冲突,和一些具体的时间信息。无法知道另外一个事务 ID 是啥。
  • 不会完整列出冲突事务的操作内容
  • 主要提供时间戳和冲突类型

通过 oplog 查询可能的事务操作

有一种思路,就是我们确认时间范围之后,可以查询 oplog 来定位是和哪个事务冲突了。

这需要注意在 MongoDB 分片集群模式下,通过 mongos 连接的时候无法访问到 local 数据库或 oplog.rs 集合。因为 local 数据库仅存在于各个独立的 shard 和 config server 上。

使用 oplog 查找相关信息

  1. 直接连接到特定的 shard 实例

    php 复制代码
    # 连接到特定的 shard primary 或者 secondary
    mongo --host shard1-host:port
    
    // 有限支持,不是所有版本都适用
    // 通过 mongos 的 adminCommand 进行特定路由
    db.adminCommand({
      $eval: "db.getSiblingDB('local').oplog.rs.find({...}).pretty()",
      nolock: true
    })
  2. 连接到 oplog 集合

    javascript 复制代码
    use local
    db.oplog.rs.find()  // 或在某些配置中为 db.oplog.$main.find()
  3. 查找事务相关操作

    javascript 复制代码
    // 搜索事务操作
    db.oplog.rs.find({
      "ts": {
        $gte: new Timestamp(<conflict_timestamp_seconds>, 0),
        $lte: new Timestamp(<conflict_timestamp_seconds + 5>, 0)
      },
      "op": { $in: ["c", "u", "d", "i"] }  // 命令、更新、删除、插入操作
    }).sort({ts: 1})
  4. 查找特定事务的操作

    javascript 复制代码
    // 如果知道事务ID (lsid),可以查找特定事务的所有操作
    db.oplog.rs.find({
      "lsid.id": UUID("<transaction_id>")
    }).sort({ts: 1})

oplog 的局限性

使用 oplog 定位事务冲突有几个重要限制:

  1. 冲突细节不明确

    • oplog 不会明确标记哪些操作导致了冲突
    • 不会记录被拒绝的操作(只记录成功的写操作)
  2. 事务操作特殊性

    • 多文档事务中的操作通常在事务提交时才会真正写入 oplog
    • 中止的事务操作不会出现在 oplog 中
  3. 时间窗口识别挑战

    • 需要根据冲突发生的时间戳附近查找可能相关的操作
    • 在高负载系统中可能有大量并发事务

实用查询示例

javascript 复制代码
// 根据错误信息中的时间戳查询相关时间段的事务操作
let errorTimestamp = Timestamp(1742240702, 12); // 从错误信息中获取
let startTs = new Timestamp(errorTimestamp.getTime() - 2, 0); // 向前查2秒
let endTs = new Timestamp(errorTimestamp.getTime() + 2, 0); // 向后查2秒

db.oplog.rs.find({
  ts: { $gte: startTs, $lte: endTs },
  $or: [
    { "o.applyOps": { $exists: true } }, // 事务操作
    { "txnNumber": { $exists: true } }   // 事务相关操作
  ]
}).sort({ts: 1})

预防措施

  1. 优化事务范围:尽量减小事务涉及的文档范围
  2. 减少事务持续时间:保持事务尽可能短
  3. 适当错开高竞争操作:考虑应用层面的策略分散写入操作
  4. 适的重试策略,并优化应用程序结构以减少冲突。
相关推荐
知识分享小能手1 天前
MongoDB入门学习教程,从入门到精通,MongoDB创建、更新和删除文档(3)
数据库·学习·mongodb
爬山算法1 天前
MongoDB(60)如何使用explain命令?
数据库·mongodb
知识分享小能手2 天前
MongoDB入门学习教程,从入门到精通,MongoDB入门指南 —— 知识点详解(2)
数据库·学习·mongodb
vpk1122 天前
使用 Docker Compose 快速安装 MongoDB
mongodb·docker·容器
爬山算法2 天前
MongoDB(55)如何监控分片集群?
数据库·mongodb
vpk1122 天前
Docker Compose 部署 Yapi(连接本地MongoDB)
mongodb·docker·yapi
jianqiang.xue3 天前
ESP32-S3 运行 Linux 全指南:从 RISC-V 模拟器移植到 8 秒快速启动
linux·stm32·单片机·mongodb·risc-v·esp32s3
知识分享小能手3 天前
MongoDB入门学习教程,从入门到精通,MongoDB 知识点详解(1)
数据库·学习·mongodb
爬山算法5 天前
MongoDB(52)如何配置分片?
数据库·mongodb
2401_858936886 天前
51 单片机核心知识点:GPIO、中断、定时器与蜂鸣器驱动
单片机·mongodb·nosql