Section 10.1: 将字段转换为其他类型并批量更新整个集合

通常情况下,我们需要将字段类型更改为其他类型,例如,原始集合中的 “数字 ”或 “日期 ”字段可能保存为字符串:

{
    "name": "Alice",
    "salary": "57871",
    "dob": "1986-08-21"
},
{
    "name": "Bob",
    "salary": "48974",
    "dob": "1990-11-04"
}

我们的目标是将上述庞大的集合更新为

{
    "name": "Alice",
    "salary": 57871,
    "dob": ISODate("1986-08-21T00:00:00.000Z")
},
{
    "name": "Bob",
    "salary": 48974,
    "dob": ISODate("1990-11-04T00:00:00.000Z")
}

对于相对较小的数据,我们可以通过使用游标的 forEach() 方法使用快照迭代集合,并按如下方式更新每个文档来实现上述目的:

db.test.find({
    "salary": { "$exists": true, "$type": 2 },
    "dob": { "$exists": true, "$type": 2 }
}).snapshot().forEach(function(doc){
    var newSalary = parseInt(doc.salary),
        newDob = new ISODate(doc.dob);
    db.test.updateOne(
        { "_id": doc._id },
        { "$set": { "salary": newSalary, "dob": newDob } }
    );
});

虽然这对于小型数据集来说是最佳选择,但对于大型数据集来说,性能就会大大降低,因为循环浏览大型数据集并将每个更新操作按请求发送到服务器会造成计算上的损失。

批量() 应用程序接口(API)提供了帮助,由于写操作只需批量发送一次到服务器,因此大大提高了性能。由于该方法不会向服务器发送每个写入请求(如 forEach() 循环中的当前更新语句),而只是每 1000 个请求中发送一次,因此实现了高效率,更新速度也比现在更快。

利用上述 forEach() 循环创建批次的相同概念,我们可以如下方式批量更新集合。在本示例中,MongoDB 版本 >= 2.6 和 < 3.2 中的 Bulk() API 使用 initializeUnorderedBulkOp() 方法并行执行批次中的写操作,而且顺序不确定。

它通过将 salary 和 dob 字段分别更改为数值和日期值来更新客户集合中的所有文档:

var bulk = db.test.initializeUnorderedBulkOp(),
    counter = 0; // counter to keep track of the batch update size

db.test.find({
    "salary": { "$exists": true, "$type": 2 },
    "dob": { "$exists": true, "$type": 2 }
}).snapshot().forEach(function(doc){
    var newSalary = parseInt(doc.salary),
        newDob = new ISODate(doc.dob);
    bulk.find({ "_id": doc._id }).updateOne({
        "$set": { "salary": newSalary, "dob": newDob }
    });

    counter++; // increment counter
    if (counter % 1000 == 0) {
        bulk.execute(); // Execute per 1000 operations and re-initialize every 1000 update statements
        bulk = db.test.initializeUnorderedBulkOp();
    }
});

下一个示例适用于新的 MongoDB 3.2 版本,该版本已经弃用了 Bulk() API,并使用 bulkWrite() 提供了一套更新的应用程序。

该示例使用了与上述相同的游标,但使用相同的 forEach() 游标方法创建了具有批量操作的数组,以便将每个批量写入文档推送到数组中。由于写入命令最多只能接受 1000 次操作,因此需要对操作进行分组,使其最多只能有 1000 次操作,并在循环迭代到 1000 次时重新初始化数组:

var cursor = db.test.find({
        "salary": { "$exists": true, "$type": 2 },
        "dob": { "$exists": true, "$type": 2 }
    }),
    bulkUpdateOps = [];

cursor.snapshot().forEach(function(doc){
    var newSalary = parseInt(doc.salary),
        newDob = new ISODate(doc.dob);
    bulkUpdateOps.push({
        "updateOne": {
            "filter": { "_id": doc._id },
            "update": { "$set": { "salary": newSalary, "dob": newDob } }
        }
    });

    if (bulkUpdateOps.length === 1000) {
        db.test.bulkWrite(bulkUpdateOps);
        bulkUpdateOps = [];
    }
});

if (bulkUpdateOps.length > 0) { db.test.bulkWrite(bulkUpdateOps); }