Khắc phục lỗi MongoDB 'cannot use the part (...) to traverse the element'

intermediate🍃 MongoDB2026-07-18| MongoDB 3.6+, Node.js (Mongoose), Python (PyMongo), MongoDB Atlas, Linux/Windows/macOS

Error Message

Plan executor error during findAndModify :: caused by :: cannot use the part (address) to traverse the element (address: "123 Main St")
#mongodb#di-tru-du-lieu#dot-notation#phat-trien-backend#nosql

Vấn đề: Sai lệch kiểu dữ liệu trong đường dẫn lồng nhauCó lẽ bạn ở đây vì một lệnh cập nhật thông thường đã bị lỗi. Lỗi này kích hoạt khi bạn cố gắng sử dụng dot notation (ký hiệu dấu chấm) để cập nhật một trường con, nhưng trường cha không phải là một đối tượng. MongoDB cố gắng 'đi sâu' vào một document, nhưng nó gặp ngõ cụt—thường là một chuỗi hoặc một số.

Hãy xem xét một tình huống phổ biến. Giả sử bạn có một document người dùng từ phiên bản cũ của ứng dụng:

{
  "_id": ObjectId("60a1b2c3d4e5f6g7h8i9j0k1"),
  "name": "John Doe",
  "address": "123 Main St"
}

Sau đó, yêu cầu của bạn thay đổi. Bạn quyết định lưu trữ dữ liệu vị trí chi tiết thay vì một chuỗi duy nhất. Bạn thực hiện lệnh cập nhật này:

db.users.updateOne(
  { "name": "John Doe" },
  { "$set": { "address.city": "New York" } }
)

MongoDB thất bại ngay lập tức. Nó không thể tìm thấy city bên trong addressaddress hiện đang là một chuỗi. Nó không thể truy xuất qua một giá trị nguyên thủy (primitive value).

Tại sao điều này xảy raCơ sở dữ liệu đang hiểu theo đúng nghĩa đen. Để truy cập address.city, khóa address phải là một vật chứa (một đối tượng). Nếu nó là một chuỗi như "123 Main St", MongoDB không biết đặt dữ liệu mới vào đâu. Sự xung đột này thường xuất phát từ ba vấn đề:

  • Tiến hóa Schema: Bạn đã nâng cấp mô hình dữ liệu của mình nhưng vẫn để lại các bản ghi cũ ở định dạng cũ.- Ô nhiễm dữ liệu: Một lỗi trong script cũ đã chèn một chuỗi vào nơi lẽ ra phải là một đối tượng.- Nhầm lẫn đường dẫn: Bạn đang nhắm mục tiêu vào một đường dẫn lồng nhau không tồn tại theo cách bạn nghĩ.## Cách khắc phục thủ công cho các Document đơn lẻNếu bạn chỉ cần sửa một hoặc hai bản ghi bị lỗi, hãy thay thế toàn bộ trường cha. Đừng cố gắng cập nhật riêng trường con. Hãy ghi đè chuỗi bằng một đối tượng mới.
db.users.updateOne(
  { "name": "John Doe" },
  { "$set": { "address": { "city": "New York", "street": "123 Main St" } } }
)

Cách này bỏ qua hoàn toàn logic truy xuất (traversal). Bạn chỉ đơn giản là hoán đổi một chuỗi bằng một đối tượng có cấu trúc.

Di trú hàng loạt cho các Collection lớnViệc sửa document thủ công là không thể nếu bạn có 50.000 bản ghi cũ. Bạn cần một script di trú để tiêu chuẩn hóa collection của mình. Điều này đảm bảo mọi document đều tuân theo schema mới trước khi bạn thực hiện các cập nhật lồng nhau.

1. Tìm các bản ghi ngoại lệĐầu tiên, hãy xác định chính xác có bao nhiêu document vẫn sử dụng định dạng chuỗi cũ:

db.users.countDocuments({ "address": { "$type": "string" } })

2. Chuyển đổi chuỗi thành đối tượng (MongoDB 4.2+)Các phiên bản MongoDB hiện đại cho phép bạn sử dụng một aggregation pipeline bên trong một lệnh update. Điều này rất mạnh mẽ vì bạn có thể tham chiếu giá trị hiện có trong khi thay đổi kiểu dữ liệu của trường.

db.users.updateMany(
  { "address": { "$type": "string" } },
  [
    {
      "$set": {
        "address": {
          "fullAddress": "$address",
          "city": "Không xác định",
          "migrated": true
        }
      }
    }
  ]
)

Trong ví dụ này, chúng ta chuyển chuỗi cũ vào một trường fullAddress mới. Chúng ta cũng thêm một giá trị giữ chỗ city để ngăn chặn các lỗi truy xuất trong tương lai.

Lập trình phòng thủ trong Node.js (Mongoose)Mã nguồn thực tế (production) nên lường trước dữ liệu không nhất quán. Nếu việc di trú của bạn chưa hoàn tất 100%, hãy sử dụng khối try-catch để xử lý sự sai lệch một cách khéo léo. Điều này ngăn chặn API của bạn trả về lỗi 500 cho người dùng.

// Một kiểu cập nhật an toàn hơn
try {
  await User.updateOne(
    { _id: userId },
    { $set: { "address.city": "New York" } }
  );
} catch (err) {
  if (err.code === 28 || err.message.includes('cannot use the part')) {
    // Phương án dự phòng: Trường này có khả năng là một chuỗi. Ghi đè nó.
    await User.updateOne(
      { _id: userId },
      { $set: { address: { city: "New York" } } }
    );
  } else {
    throw err;
  }
}

Xác minh và Phòng ngừaSau khi bạn đã áp dụng bản sửa lỗi, hãy xác minh rằng đường dẫn "truy xuất" (traversal) đã thông suốt cho tất cả các document. Chạy truy vấn này:

db.users.find({ "address": { "$exists": true, "$not": { "$type": "object" } } })

Nếu kết quả trả về bằng không, collection của bạn đã sạch. Để duy trì điều đó, hãy cân nhắc triển khai MongoDB Schema Validation. Bạn có thể thiết lập một quy tắc buộc trường address luôn phải là một object, từ chối mọi nỗ lực chèn một chuỗi trong tương lai.

Related Error Notes