Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> MongoDB

Cách thêm thuộc tính mới cho mỗi document trong collection MongoDB lớn

Giới thiệu

Để thêm trường (property) mới cho tất cả document trong một collection lớn, bạn dùng updateMany() (hiện đại) hoặc forEach() với updateOne() (cũ). Với collection rất lớn, cần xử lý theo batch để tránh timeout và giảm tải server.

Ví dụ minh họa

1. Tạo collection

> db.themThuocTinh.insertOne({ "TenSinhVien": "John", "TuoiSinhVien": 23, "QuocGia": "US" });
{ "acknowledged" : true, "insertedId" : ObjectId("5ca1e61866324ffac2a7dc56") }
> db.themThuocTinh.insertOne({ "TenSinhVien": "David", "TuoiSinhVien": 21, "QuocGia": "AUS" });
{ "acknowledged" : true, "insertedId" : ObjectId("5ca1e62366324ffac2a7dc57") }
> db.themThuocTinh.insertOne({ "TenSinhVien": "Bob", "TuoiSinhVien": 21, "QuocGia": "UK" });
{ "acknowledged" : true, "insertedId" : ObjectId("5ca1e62d66324ffac2a7dc58") }

2. Thêm trường TenSinhVienInUpperCase = uppercase(TenSinhVien)

> db.themThuocTinh.find().forEach(function(doc) {
    db.themThuocTinh.updateOne(
        { _id: doc._id },
        { $set: { TenSinhVienInUpperCase: doc.TenSinhVien.toUpperCase() } }
    );
});

3. Kết quả

{ "_id" : ObjectId("5ca1e61866324ffac2a7dc56"), "TenSinhVien" : "John", "TuoiSinhVien" : 23, "QuocGia" : "US", "TenSinhVienInUpperCase" : "JOHN" }
{ "_id" : ObjectId("5ca1e62366324ffac2a7dc57"), "TenSinhVien" : "David", "TuoiSinhVien" : 21, "QuocGia" : "AUS", "TenSinhVienInUpperCase" : "DAVID" }
{ "_id" : ObjectId("5ca1e62d66324ffac2a7dc58"), "TenSinhVien" : "Bob", "TuoiSinhVien" : 21, "QuocGia" : "UK", "TenSinhVienInUpperCase" : "BOB" }

Phương pháp hiện đại: updateMany với Aggregation Pipeline (MongoDB 4.2+)

Hiệu quả hơn, atomic, không cần vòng lặp:

db.themThuocTinh.updateMany(
    {}, // Tất cả document
    [
        { $set: { 
            TenSinhVienInUpperCase: { $toUpper: "$TenSinhVien" }
        } }
    ]
);

Xử lý Collection LỚN (Batch Processing)

Với collection hàng triệu document, dùng batch để tránh timeout và OOM:

Cách 1: Cursor batchSize + forEach (Shell)

var batchSize = 1000;
var count = 0;

db.themThuocTinh.find({ TenSinhVienInUpperCase: { $exists: false } })
    .batchSize(batchSize)
    .forEach(function(doc) {
        db.themThuocTinh.updateOne(
            { _id: doc._id },
            { $set: { TenSinhVienInUpperCase: doc.TenSinhVien.toUpperCase() } }
        );
        count++;
        if (count % 10000 === 0) {
            print("Đã xử lý: " + count);
            sleep(100); // Nghỉ 100ms để giảm tải
        }
    });

print("Hoàn tất: " + count + " documents");

Cách 2: Bulk Operations (Hiệu quả nhất)

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

db.themThuocTinh.find({ TenSinhVienInUpperCase: { $exists: false } }).forEach(function(doc) {
    bulk.find({ _id: doc._id }).updateOne({
        $set: { TenSinhVienInUpperCase: doc.TenSinhVien.toUpperCase() }
    });
    count++;
    
    if (count % 1000 === 0) {
        bulk.execute();
        bulk = db.themThuocTinh.initializeUnorderedBulkOp();
    }
});

if (count % 1000 !== 0) {
    bulk.execute();
}

Cách 3: Application code (Node.js/Python) - Khuyên dùng cho production

// Node.js example
const cursor = collection.find({ newField: { $exists: false } }).batchSize(1000);
const bulkOps = [];

for await (const doc of cursor) {
    bulkOps.push({
        updateOne: {
            filter: { _id: doc._id },
            update: { $set: { newField: doc.existingField.toUpperCase() } }
        }
    });
    
    if (bulkOps.length >= 1000) {
        await collection.bulkWrite(bulkOps);
        bulkOps.length = 0;
    }
}
if (bulkOps.length > 0) await collection.bulkWrite(bulkOps);

So sánh phương pháp

Phương phápƯu điểmNhược điểmPhù hợp
updateMany + pipelineAtomic, nhanh, code gọnChỉ MongoDB 4.2+, logic phức tạp hạn chếCollection < 100k doc
forEach + updateOneĐơn giản, logic JS tùy ýChậm, single-threaded, risk timeoutCollection nhỏ, ad-hoc
Bulk WriteNhanh, kiểm soát batch, ít round-tripPhức tạp hơnCollection lớn, production

Lưu ý quan trọng

    Luôn **test trên staging** trước khi chạy production
  • Thêm filter { newField: { $exists: false } } để idempotent (chạy lại an toàn)
  • Monitor db.currentOp() và db.serverStatus().opcounters khi chạy
  • Cân nhắc chạy ở off-peak hours
  • Backup trước khi update hàng loạt