MongoDB oplog的记录在键名中带有点,无法查询,afaict



给定:Mongo允许使用"点"设置嵌套字段,例如:

rs0:PRIMARY> db.tmp.update({ a: 1 }, { $set: { 'b.c': 2 } }, { upsert: true })
rs0:PRIMARY> db.tmp.findOne()
{
    "_id" : ObjectId("558251c6a3354af70d70f3cc"),
    "a" : 1,
    "b" : {
        "c" : 2
    }
}

在这个例子中,记录是由 upsert 创建的,我可以在 oplog 中验证:

rs0:PRIMARY> use local
rs0:PRIMARY> db.oplog.rs.find().sort({ts:-1}).limit(1).pretty()
{
    "ts" : Timestamp(1434603974, 2),
    "h" : NumberLong("2071516013149720999"),
    "v" : 2,
    "op" : "i",
    "ns" : "test.tmp",
    "o" : {
        "_id" : ObjectId("558251c6a3354af70d70f3cc"),
        "a" : 1,
        "b" : {
            "c" : 2
        }
    }
}

当我做同样的事情并且记录只是更新而不是创建时,我似乎得到了相同的行为:

rs0:PRIMARY> db.tmp.update({ a: 1 }, { $set: { 'b.d': 3 } }, { upsert: true })
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
rs0:PRIMARY> db.tmp.findOne()
{
    "_id" : ObjectId("558251c6a3354af70d70f3cc"),
    "a" : 1,
    "b" : {
        "c" : 2,
        "d" : 3
    }
}

但是,这次oplog中的条目结构不同:

rs0:PRIMARY> use local
rs0:PRIMARY> db.oplog.rs.find().sort({ts:-1}).limit(1).pretty()
{
    "ts" : Timestamp(1434604173, 1),
    "h" : NumberLong("-4353495487634403370"),
    "v" : 2,
    "op" : "u",
    "ns" : "test.tmp",
    "o2" : {
        "_id" : ObjectId("558251c6a3354af70d70f3cc")
    },
    "o" : {
        "$set" : {
            "b.d" : 3
        }
    }
}

(请注意"b.d"键)。

这给我带来了问题,因为我正在尝试通过检查相应的oplog条目来调查一些丢弃的更新,但是AFAICT无法查询设置特定嵌套字段的oplog条目:

rs0:PRIMARY> db.oplog.rs.findOne({ 'o.$set.b.d': { $exists: true } })
null

有没有办法在oplog中查询与特定嵌套字段更新相关的条目(在本例中为b.d)?

似乎我遇到了 Mongo 禁止在字段名称中使用点的不一致应用:一方面我无法创建(通过官方客户端/直接在 Mongo shell 中)或查询它们,但另一方面它在 oplog 中创建它们,留下无法查询的 oplog 条目。

任何帮助将不胜感激。

为了完整起见,请注意,我可以使用包含$set位的键成功查询oplog条目:

rs0:PRIMARY> db.tmp.update({ a: 1 }, { $set: { e: 4 } }, { upsert: true })
rs0:PRIMARY> use local
rs0:PRIMARY> db.oplog.rs.findOne({ 'o.$set.e': { $exists: true } })
{
    "ts" : Timestamp(1434604486, 1),
    "h" : NumberLong("1819316318253662899"),
    "v" : 2,
    "op" : "u",
    "ns" : "test.tmp",
    "o2" : {
        "_id" : ObjectId("558251c6a3354af70d70f3cc")
    },
    "o" : {
        "$set" : {
            "e" : 4
        }
    }
}

你是对的,MongoDB的oplog实现中存在一些不一致,它允许每个操作日志的文档格式在技术上不允许相应地查询这样的文档。

即使插入相同的条目也是不可能的,因为它具有$set字段名称:

db.tmp2.insert({ 
    "ts" : Timestamp(1450117240, 1), 
    "h" : NumberLong(2523649590228245285), 
    "v" : NumberInt(2), 
    "op" : "u", 
    "ns" : "test.tmp", 
    "o2" : {
        "_id" : ObjectId("566f069e63d6a355b2c446af")
    }, 
    "o" : {
        "$set" : {
            "b.d" : NumberInt(4)
        }
    }
})
2015-12-14T10:27:04.616-0800 E QUERY    Error: field names cannot start with $ [$set]
    at Error (<anonymous>)
    at DBCollection._validateForStorage (src/mongo/shell/collection.js:161:19)
    at DBCollection._validateForStorage (src/mongo/shell/collection.js:165:18)
    at insert (src/mongo/shell/bulk_api.js:646:20)
    at DBCollection.insert (src/mongo/shell/collection.js:243:18)
    at (shell):1:9 at src/mongo/shell/collection.js:161

并且 b.d 对密钥无效

db.tmp.update({ a: 1 }, { $set: { 'b.d': 4 } }, { upsert: true })
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
db.oplog.rs.find()
db.tmp2.insert({ 
    "ts" : Timestamp(1450117240, 1), 
    "h" : NumberLong(2523649590228245285), 
    "v" : NumberInt(2), 
    "op" : "u", 
    "ns" : "test.tmp", 
    "o2" : {
        "_id" : ObjectId("566f069e63d6a355b2c446af")
    }, 
    "o" : {
        "set" : {
            "b.d" : NumberInt(4)
        }
    }
})
2015-12-14T10:23:26.491-0800 E QUERY    Error: can't have . in field names [b.d]
    at Error (<anonymous>)
    at DBCollection._validateForStorage (src/mongo/shell/collection.js:157:19)
    at DBCollection._validateForStorage (src/mongo/shell/collection.js:165:18)
    at DBCollection._validateForStorage (src/mongo/shell/collection.js:165:18)
    at insert (src/mongo/shell/bulk_api.js:646:20)
    at DBCollection.insert (src/mongo/shell/collection.js:243:18)
    at (shell):1:9 at src/mongo/shell/collection.js:157

也许应该记录一个 Jira 问题,该问题建议将$set搜索的语法设置为值:

{ 
    "ts" : Timestamp(1450117240, 1), 
    "h" : NumberLong(2523649590228245285), 
    "v" : NumberInt(2), 
    "op" : "u", 
    "ns" : "test.tmp", 
    "o2" : {
        "_id" : ObjectId("566f069e63d6a355b2c446af")
    }, 
    "o" : {
        "$set" : {
            "key" : "b.d"
            "value" : NumberInt(4)
        }
    }
}

更新:为此创建了一个 Jira 问题:

https://jira.mongodb.org/browse/SERVER-21889

虽然您确实无法通过查找直接查询"b.d",但该问题有解决方法(由于您尝试这样做是为了调试目的,因此解决方法将允许您找到与所需更新格式匹配的所有记录)。

编辑 有关聚合解决方法,请参阅答案底部。

使用 mapReduce 输出要匹配的oplog记录的 ts(时间戳)值:

map = function() {
    for (i in this.o.$set) 
       if (i=="b.d") emit(this.ts, 1);
}
reduce = function(k, v) { return v; }
db.oplog.rs.mapReduce(map,reduce,{out:{inline:1},query:{op:"u","o.$set":{$exists:true}}})
{
  "results" : [
    {
        "_id" : Timestamp(1406409018, 1),
        "value" : 1
    },
    {
        "_id" : Timestamp(1406409030, 1),
        "value" : 1
    },
    {
        "_id" : Timestamp(1406409042, 1),
        "value" : 1
    },
    {
        "_id" : Timestamp(1406409053, 1),
        "value" : 1
    }
  ],
  "timeMillis" : 117,
  "counts" : {
    "input" : 9,
    "emit" : 4,
    "reduce" : 0,
    "output" : 4
  },
  "ok" : 1
}
db.oplog.rs.find({ts:{$in:[Timestamp(1406409018, 1), Timestamp(1406409030, 1), Timestamp(1406409042, 1), Timestamp(1406409053, 1)]}})
< your results if any here >

在地图函数中,将"b.d"替换为您要查找的虚线字段名称。

如果你想花哨,你可以map一个常量并发出"$in"文档,然后在查询中使用它(相同的结果,格式略有不同):

map2=function () {
   for (i in this.o.$set)
      if (i=="b.d") emit(1, {"$in": [ this.ts ]});
}
reduce2=function (k, v) {
   result={"$in": [ ] };
   v.forEach(function(val) {
      val.$in.forEach(function(ts) {
          result.$in.push(ts);
      });
   });
   return result;
}

我可以在 shell 中运行这个版本,在我的测试数据上得到这样的结果:

tojsononeline(db.oplog.rs.mapReduce(map2, reduce2, { out:{inline:1}, query:{op:"u","o.$set":{$exists:true}}}).results[0].value)
{  "$in" : [ Timestamp(1406409042, 1), Timestamp(1406409018, 1), Timestamp(1406409030, 1), Timestamp(1406409053, 1) ] }

编辑 事实证明,还有一种方法可以直接通过聚合框架运行查询:

db.oplog.rs.aggregate( [
    {$match:{"o.$set":{$exists:true}}},
    {$project: { doc:"$$ROOT", 
                 matchMe:{$eq:["$o",{$literal:{$set:{"b.d":1 }}}]}
    }},
    {$match:{matchMe:true}}
] ).pretty()
< your matching records if any >

最新更新