有没有办法恢复MongoDB中最近删除的文件?

时间:2014-09-12 07:15:25

标签: mongodb rollback

我错误地删除了上次查询中的一些文档,有没有办法回滚我的上一个查询mongo集合。

这是我的上一次查询:

 db.datas.remove({ "name" : "some_x_name"}) 

是否有回滚/撤消选项?我能收回数据吗?

2 个答案:

答案 0 :(得分:23)

在MongoDB上下文中没有回滚选项(rollback has a different meaning),严格来说没有支持的方法来获取这些文档 - 评论中涵盖了您可以/应该采取的预防措施。但是,如果您正在运行副本集,甚至是单个节点副本集,那么您将拥有oplog。使用oplog覆盖文档插入时,您可以恢复它们。

说明这一点的最简单方法是举个例子。我将使用一个简化的示例,只需要恢复100个已删除的文档。要超越这个(大量文档,或者您希望只选择性地恢复等),您将要么更改代码以迭代游标,或者使用您在MongoDB shell之外选择的语言来编写它。基本逻辑保持不变。

首先,让我们在数据库foo中创建我们的示例集合dropTest。我们将插入100个没有name字段的文档和100个具有相同name字段的文档,以便以后可能会错误地将其删除:

use dropTest;
for(i=0; i < 100; i++){db.foo.insert({_id : i})};
for(i=100; i < 200; i++){db.foo.insert({_id : i, name : "some_x_name"})};

现在,让我们模拟意外删除我们的100个name文档:

> db.foo.remove({ "name" : "some_x_name"})
WriteResult({ "nRemoved" : 100 })

因为我们在副本集中运行,所以我们仍然在oplog(正在插入)中记录这些文档,并且幸运的是,这些插入还没有(但是)从{{1}的末尾掉落(oplogcapped collection记得)。让我们看看我们是否能找到它们:

oplog

计数看起来正确,我们似乎还有我们的文件。我从经验中知道,我们在这里需要use local; db.oplog.rs.find({op : "i", ns : "dropTest.foo", "o.name" : "some_x_name"}).count(); 100 条目的唯一部分是oplog字段,所以让我们添加一个投影,只返回它(输出为了简洁而剪断,但你明白了) :

o

要重新插入这些文档,我们可以将它们存储在一个数组中,然后迭代数组并插入相关的部分。首先,让我们创建我们的数组:

db.oplog.rs.find({op : "i", ns : "dropTest.foo", "o.name" : "some_x_name"}, {"o" : 1});
{ "o" : { "_id" : 100, "name" : "some_x_name" } }
{ "o" : { "_id" : 101, "name" : "some_x_name" } }
{ "o" : { "_id" : 102, "name" : "some_x_name" } }
{ "o" : { "_id" : 103, "name" : "some_x_name" } }
{ "o" : { "_id" : 104, "name" : "some_x_name" } }

接下来我们提醒自己,我们现在只有100个文档,然后遍历100个插入,最后重新验证我们的计数:

var deletedDocs = db.oplog.rs.find({op : "i", ns : "dropTest.foo", "o.name" : "some_x_name"}, {"o" : 1}).toArray();
> deletedDocs.length
100

你有它,但有一些警告:

  • 这并不是真正的恢复策略,请查看备份(MMS,其他),延迟的辅助,如评论中所述
  • 在繁忙的大型系统上查询oplog中的文档(任何oplog查询是表扫描)都不会特别快。
  • 文档可能会随时老化(当然,您可以制作一份oplog副本供以后使用,以便为您提供更多时间)
  • 根据您的工作量,您可能需要在重新插入结果之前删除结果
  • 如图所示,较大的文档集对于数组来说太大了,因此您需要迭代光标而不是
  • use dropTest; db.foo.count(); 100 // simple for loop to re-insert the relevant elements for (var i = 0; i < deletedDocs.length; i++) { db.foo.insert({_id : deletedDocs[i].o._id, name : deletedDocs[i].o.name}); } // check total and name counts again db.foo.count(); 200 db.foo.count({name : "some_x_name"}) 100 的格式被视为内部格式,可能随时更改(恕不另行通知),因此使用风险自负

答案 1 :(得分:7)

虽然我知道这有点旧,但我想分享一些我在这方面研究过的东西,这些东西可能对有类似问题的其他人有用。

事实是,MongoDB不会立即物理删除数据 - 它只会将其标记为删除。然而,这是特定于版本的,并且目前没有文档或标准化 - 这可以使第三方工具开发人员(或迫切需要的人)能够构建工具或可靠地编写可跨版本工作的简单脚本。我为此开了一张票 - https://jira.mongodb.org/browse/DOCS-5151

我确实探索了一个较低级别的选项,可能需要根据所使用的MongoDB版本进行微调。可以理解的是,对于大多数人来说,链接的级别太低,但是当其他所有方法都失败时,它可以工作并且可以很方便。

我的方法涉及直接使用文件中的二进制文件,并使用Python脚本(或命令)来识别,读取和解包(BSON)已删除的数据。

我的方法受到this GitHub项目的启发(我不是这个项目的开发者)。 Here on my blog我试图简化脚本并从Raw MongoDB文件中提取特定的已删除记录。

目前,记录被标记为删除为&#34; \xee&#34;在记录的开头。这是原始db文件中删除的记录的样子,

‘\xee\xee\xee\xee\x07_id\x00U\x19\xa6g\x9f\xdf\x19\xc1\xads\xdb\xa8\x02name\x00\x04\x00\x00\x00AAA\x00\x01marks\x00\x00\x00\x00\x00\x00@\x9f@\x00′

我用先前根据其他记录确定的记录大小替换了第一个块。

y=”3\x00\x00\x00″+x[20804:20800+51]

最后使用BSON包(pymongo附带),我将二进制文件解码为Readable对象。

bson.decode_all(y)

[{u’_id': ObjectId(‘5519a6679fdf19c1ad73dba8′), u’name': u’AAA’, u’marks': 2000.0}]

这个BSON现在是一个python对象,可以转储到恢复集合中,或者只是记录在某个地方。

不用说,理想情况下,应该在数据库文件的备份副本的暂存区域中完成此操作或任何其他恢复技术。