Thông báo lỗiCó lẽ bạn đang cố gắng tối ưu hóa một truy vấn tìm kiếm thì MongoDB gặp lỗi. Lỗi này xảy ra ngay khi bạn cố gắng tạo một index phức hợp (compound index) trên hai trường kiểu mảng khác nhau:
MongoServerError: cannot index parallel arrays [tags] [categories]
Cho dù bạn đang chạy một script migration hay lệnh shell thủ công, thao tác này sẽ thất bại ngay lập tức. Đây không phải là lỗi phần mềm; đó là một giới hạn an toàn được thiết lập sẵn trong engine lập chỉ mục của MongoDB.
Tại sao MongoDB chặn điều nàyMongoDB tạo một mục index cho từng phần tử trong một mảng (gọi là 'multikey index'). Khi bạn cố gắng kết hợp hai mảng trong một index duy nhất, MongoDB đối mặt với một "cơn ác mộng" toán học: tích Descartes (Cartesian product).
Nếu một tài liệu có 10 tag và 5 category, MongoDB phải tạo 50 mục index riêng biệt chỉ cho bản ghi đó. Ở quy mô thực tế với 1 triệu tài liệu, một index đơn giản có thể bùng nổ thành 50 triệu mục. Sự tăng trưởng khổng lồ này sẽ làm cạn kiệt bộ nhớ RAM và làm giảm đáng kể hiệu suất ghi của bạn.
Tình huống thực tếGiả sử collection products của bạn chứa các tài liệu có cấu trúc như sau:
{
"name": "Ghế công thái học",
"tags": ["văn phòng", "nội thất", "giảm giá"],
"categories": ["nhà ở", "chuyên nghiệp"]
}
Bạn chạy lệnh này để tối ưu hóa bộ lọc trên dashboard:
db.products.createIndex({ tags: 1, categories: 1 })
Vì cả tags và categories đều là mảng, MongoDB từ chối tạo index để ngăn chặn việc sụt giảm hiệu suất.
Cách khắc phục lỗiBạn không thể ép buộc MongoDB lập chỉ mục hai mảng trong một compound index. Thay vào đó, hãy chọn một chiến lược phù hợp với các mẫu truy vấn cụ thể của bạn.
Tùy chọn 1: Sử dụng các Index riêng biệtTạo hai index riêng lẻ thay vì một compound index. Trình tối ưu hóa của MongoDB đủ thông minh để sử dụng Index Intersection (Giao điểm Index) nhằm đáp ứng các truy vấn sử dụng cả hai trường.
// Tạo hai index độc lập
db.products.createIndex({ tags: 1 })
db.products.createIndex({ categories: 1 })
Mặc dù cách này chậm hơn một chút so với một compound index hoàn hảo, nhưng nó giải quyết được vấn đề cho 90% trường hợp sử dụng mà không cần thiết kế lại schema.
Tùy chọn 2: Index một mảng và một trường đơn (Scalar)MongoDB cho phép các compound index bao gồm duy nhất một mảng. Nếu một trong các trường của bạn thực tế là một chuỗi hoặc số đơn lẻ (scalar), hãy đảm bảo dữ liệu của bạn phản ánh đúng điều đó.
Nếu bạn bắt buộc phải giữ cả hai là mảng, hãy lập chỉ mục cho trường giúp thu hẹp kết quả tìm kiếm nhiều nhất (trường có tính 'chọn lọc' cao nhất) và thêm một trường không phải mảng như status hoặc price vào index:
// Kết hợp một mảng với một chuỗi trạng thái (status)
db.products.createIndex({ tags: 1, status: 1 })
Tùy chọn 3: Thiết kế lại Schema (Nhúng Object)Đối với các truy vấn cần hiệu suất cao mà bạn luôn lọc theo các cặp giá trị cụ thể, hãy cấu trúc lại dữ liệu của bạn thành một mảng các object duy nhất.
// Cấu trúc cũ: tags: ["A", "B"], categories: ["X", "Y"]
// Cấu trúc mới:
// metadata: [{ t: "A", c: "X" }, { t: "B", c: "Y" }]
// Index các trường con (sub-fields)
db.products.createIndex({ "metadata.t": 1, "metadata.c": 1 })
Cách này vượt qua giới hạn vì metadata là mảng duy nhất. MongoDB lập chỉ mục các cặp giá trị trực tiếp mà không cần tính toán mọi kết hợp có thể xảy ra.
Xác minh kết quảSau khi áp dụng giải pháp đã chọn, hãy xác nhận rằng các truy vấn của bạn thực sự hiệu quả. Chạy lệnh explain() trên bộ lọc của bạn:
db.products.find({
tags: "nội thất",
categories: "nhà ở"
}).explain("executionStats")
Tìm giai đoạn IXSCAN trong winningPlan. Nếu bạn thấy COLLSCAN, điều đó có nghĩa là truy vấn của bạn vẫn đang quét mọi tài liệu trong collection—một dấu hiệu cảnh báo nghiêm trọng về hiệu suất.

