查询在mongoDB中将字符串更改为int需要很长时间

时间:2014-09-03 08:59:15

标签: javascript node.js mongodb mongodb-query

我在mongoDB中对我的整个数据集(大约300万个文档)运行以下查询,以将字符串的用户ID更改为整数。此查询似乎没有完成:

var cursor = db.play_sessions.find()

while (cursor.hasNext()) { 
  var play = cursor.next(); 
  db.play_sessions.update({_id : play._id}, {$set : {user_id : new NumberInt(play.user_id) }});
}

我在同一个数据集上运行此查询,它返回的速度相对较快:

db.play_sessions.find().forEach(function(play){
    if (play.score && play.user_id && play.user_attempt_no && play.game_id && play.level && play.training_session_id) {
        print(play.score,",",play.user_id,",",play.user_attempt_no,",",play.game_id,",",play.level,",",parseInt(play.training_session_id).toFixed());
    } else if (play.score && play.user_id && play.user_attempt_no && play.game_id && play.level) {
        print(play.score,",",play.user_id,",",play.user_attempt_no,",",play.game_id,",",play.level);
    };
});

我理解我在第一个查询中写入数据库,但为什么第一个查询似乎永远不会返回,而第二个查询相对较快?第一个查询中的代码有问题吗?

1 个答案:

答案 0 :(得分:1)

300万份文件是相当多的文件,因此整个操作需要一段时间。但是要考虑的主要问题是你要求将数据“发送”到数据库并“接收”一个确认的写入响应(因为这就是发生的事情)三百万次。仅仅这一点就在操作之间等待,而不是简单地迭代游标。

此处的另一个原因是您很可能正在运行MongoDB 2.6或更高版本。早期版本和版本之间存在核心差异,直到在shell中处理此代码的方式。其核心是Bulk Operations API,其中包含所有shell帮助程序实际使用的方法,用于所有与数据库的交互。

在先前版本中,在这种“循环”操作中,对于每次迭代,在该上下文中没有进行“写入关注”确认。它现在的方式(因为帮助程序实际上使用Bulk API),每次迭代都会返回确认。这会减慢很多事情。除非您当然直接使用批量操作。

所以要在现代版本中“重新塑造”你的价值观,请改为:

var bulk = db.play_sessions.initializeUnorderedBulkOp();
var count = 0;

db.play_sessions.find({ "user_id": { "$type": 2 } }).forEach(function(doc) {
    bulk.find({ "_id": doc._id }).updateOne({
        "$set": { "user_id": NumberInt(doc.user_id) }
    });
    count++;

    if ( count % 10000 == 0 ) {
        bulk.execute();
        bulk = db.play_sessions.initializeUnorderedBulkOp();
    }
});

if ( count % 10000 != 0 ) 
    bulk.execute();

批量操作在单个请求中发送所有“批次”。事实上,底层驱动程序将其分解为1000个项目的单个批处理请求,但10000是一个合理的数字,在大多数情况下不会占用太多内存。

此处的其他优化是,查询选择的唯一项目是 $type 运算符当前为“字符串”的项目,以识别此项。如果某些数据已经转换,这可能会加快速度。

如果您确实拥有较早版本的MongoDB,并且您在不在分片群集上的集合上运行此转换,那么您的另一个选择是使用db.eval()

关注以实际读取该链接上的内容。这不是一个好主意,你不应该在生产中使用它,而只能作为一次性转换的最后手段。代码以JavaScript的形式提交,并实际在服务器上运行。因此,在运行时可以并且将发生高水平的锁定。您已经警告

db.eval(function() {
    db.play_sessions.find({ "user_id": { "$type": 2 } }).forEach(function(doc) {
        db.play_sessions.update(
            { "_id": doc._id },
            { "$set": { "user_id": NumberInt( doc.user_id ) } }
        );
    });
});

请谨慎使用,并希望机器上的“批处理”处理甚至是基本循环尽可能在网络术语中与实际数据库服务器尽可能接近。最好是在服务器上。

此外,如果版本允许,您仍然认为必要的eval情况,请尝试使用批量操作方法,因为这是大大优化的方法。