Cách khắc phục lỗi 'non-id update' trong Sharded Collection của MongoDB

intermediate🍃 MongoDB2026-04-25| MongoDB Sharded Cluster (mongos), phiên bản 3.4, 4.x, 5.0+, 6.0+

Error Message

Illegal Operation: cannot perform a non-id update on a sharded collection without the shard key
#mongodb#sharding#shard-key#toi-uu-hoa-co-so-du-lieu

Tại sao các bản cập nhật của bạn thất bại sau khi ShardingViệc chuyển từ một instance độc lập sang một sharded cluster làm thay đổi các quy tắc về cách bạn tương tác với dữ liệu. Gần đây, tôi đã hỗ trợ một đội ngũ di chuyển một collection 500GB sang một cụm 3-shard. Mã nguồn ứng dụng của họ, vốn đã hoạt động hoàn hảo trong nhiều năm, bỗng nhiên gặp lỗi này:

Illegal Operation: cannot perform a non-id update on a sharded collection without the shard key

Điều này xảy ra do lớp điều phối (mongos) không còn nhìn vào một thùng chứa dữ liệu duy nhất nữa. Nếu không có _id hoặc shard key, mongos sẽ không biết shard nào đang chứa tài liệu của bạn. Để tìm thấy nó, MongoDB sẽ phải truy vấn mọi shard trong cụm—một quá trình được gọi là 'scatter-gather'—và sau đó cố gắng thực hiện cập nhật. Đối với các thao tác trên một tài liệu duy nhất, MongoDB chặn điều này một cách rõ ràng để ngăn chặn hiệu suất của cụm bị giảm sút nghiêm trọng.

Vấn đề "Scatter-Gather"Trong một thiết lập sharding, dữ liệu của bạn được chia thành các mảnh (chunk) dựa trên shard key của bạn. Khi bạn chạy updateOne(), replaceOne(), hoặc findAndModify(), cụm yêu cầu một mục tiêu rõ ràng. Bạn phải cung cấp một trong các thông tin sau:

  • Trường _id:_id là duy nhất trên toàn hệ thống, mongos có thể sử dụng metadata nội bộ để xác định chính xác shard.- Shard Key: Việc cung cấp shard key cho phép bộ định tuyến (router) đi thẳng tới phân vùng chính xác.Nếu bộ lọc của bạn bỏ qua cả hai và bạn không thực hiện cập nhật hàng loạt (multi-update), thao tác sẽ luôn thất bại. Đây là một cơ chế bảo vệ, không phải lỗi.

Cách khắc phục lỗi### Cách 1: Bao gồm Shard Key trong bộ lọc của bạnĐây là cách sửa lỗi phổ biến nhất. Giả sử collection của bạn được phân mảnh theo tenant_id. Ngay cả khi bạn đang lọc theo trường email duy nhất, bạn vẫn phải bao gồm tenant_id để bộ định tuyến biết shard nào cần giao tiếp.

Mã lỗi:

// Shard key là { "region": 1 }
db.users.updateOne(
  { "email": "dev@example.com" }, 
  { $set: { "active": true } }
);

Mã đã sửa:

db.users.updateOne(
  { 
    "email": "dev@example.com", 
    "region": "EMEA" // Shard key được thêm vào đây
  }, 
  { $set: { "active": true } }
);

Cách 2: Nhắm mục tiêu tài liệu bằng _idCập nhật theo _id là tiêu chuẩn vàng cho hiệu suất. Nếu logic ứng dụng của bạn cho phép, hãy lấy ID của tài liệu trước. Một bản cập nhật _id có mục tiêu thường hoàn thành trong chưa đầy 5ms, trong khi tìm kiếm quảng bá (broadcast search) có thể mất 100ms hoặc hơn tùy thuộc vào kích thước cụm.

db.users.updateOne(
  { "_id": ObjectId("654321098765432109876543") }, 
  { $set: { "active": true } }
);

Cách 3: Chuyển sang updateManyNếu bạn thực sự cần cập nhật nhiều tài liệu—hoặc nếu bạn không bận tâm đến việc giảm hiệu suất do quảng bá—hãy sử dụng updateMany(). MongoDB cho phép các bản cập nhật quảng bá khi chúng được đánh dấu rõ ràng là "multi" vì nó giả định rằng thao tác này có ý định trải rộng trên toàn bộ cụm.

db.users.updateMany(
  { "email": "dev@example.com" }, 
  { $set: { "status": "updated" } }
);

Cảnh báo: Hãy sử dụng cách này một cách tiết kiệm. Trong một cụm có hơn 10 shard, các lệnh gọi updateMany thường xuyên mà không có shard key sẽ gây ra tình trạng tăng vọt CPU đáng kể trên các node của bạn.

Cách 4: Xử lý UpsertUpsert đặc biệt nghiêm ngặt. Nếu upsert: true được thiết lập, bạn phải bao gồm shard key. Nếu tài liệu không tồn tại, MongoDB cần key đó để quyết định shard nào sẽ sở hữu bản ghi mới.

db.users.updateOne(
  { "username": "admin_user", "site_id": 42 }, // 'site_id' là shard key
  { $set: { "lastSeen": new Date() } },
  { upsert: true }
);

Xác minh bản sửa lỗiKiểm tra đối tượng kết quả được trả về từ driver của bạn. Bạn muốn thấy matchedCount là 1. Trong Mongo shell, một bản cập nhật có mục tiêu thành công sẽ trông như thế này:

{
  "acknowledged" : true,
  "matchedCount" : 1,
  "modifiedCount" : 1
}

Nếu matchedCount là 0 nhưng bạn biết tài liệu có tồn tại, hãy kiểm tra lại xem giá trị shard key trong bộ lọc của bạn có chính xác hay không.

Lời khuyên chuyên gia cho môi trường ShardingĐể giữ cho môi trường production của bạn ổn định, tôi khuyên bạn nên thực hiện các thói quen sau:

  • Kiểm tra Key của bạn: Chạy sh.status() hoặc db.collection.getShardDistribution() trước khi viết các truy vấn mới. Đừng đoán shard key là gì.- Áp dụng Schema: Nếu bạn sử dụng Mongoose hoặc một ODM tương tự, hãy định nghĩa shard key trong schema của bạn. Điều này bắt buộc thư viện phải tự động bao gồm key trong các truy vấn của bạn.- Kiểm tra với Hash: Đối với các collection sử dụng hashed shard keys, tôi thường sử dụng Trình tạo Hash tại ToolCraft. Nó giúp tạo các chuỗi MD5 hoặc SHA-256 mẫu để kiểm tra cách dữ liệu phân phối trên các chunk trong giai đoạn phát triển.- Ghi log các Key bị thiếu: Nếu lỗi này xuất hiện trong log của bạn, thường là do một biến được truyền vào bộ lọc của bạn bị null hoặc undefined. Hãy thêm một lớp kiểm tra để bắt các shard key trống trước khi chúng chạm tới cơ sở dữ liệu.Bằng cách đảm bảo các bản cập nhật của bạn luôn có mục tiêu, bạn sẽ giữ cho cơ sở dữ liệu của mình nhanh chóng và độ trễ của ứng dụng ở mức thấp.

Related Error Notes