MongoDB oplog的键名称中包含点的记录,无法查询,

时间:2015-06-18 05:27:32

标签: mongodb

鉴于:Mongo允许使用" dot" s设置嵌套字段,例如:

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 prohibition of dots in field-names的不一致应用:一方面我无法创建(通过官方客户/直接在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
        }
    }
}

2 个答案:

答案 0 :(得分:3)

你是对的,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 search的语法设置为值:

{ 
    "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

答案 1 :(得分:1)

虽然你无法查询&#34; b.d&#34;直接通过查找有问题的解决方法(因为您尝试执行此操作以进行调试),解决方法将允许您查找与所需更新格式匹配的所有记录。

编辑请参阅答案的底部以了解汇总解决方法。

使用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 >

在地图功能中,替换&#34; b.d&#34;使用您要查找的虚线字段名称。

如果您想获得幻想,可以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 >