أي تطبيق يربط MongoDB بفهرس unique (بريد إلكتروني، اسم مستخدم، رقم طلب) يصطدم بهذه الرسالة عاجلًا أو آجلًا:
E11000 duplicate key error collection: store.users index: email_1
dup key: { email: "user@example.com" }
الرسالة أوضح ممّا تبدو — فيها كل ما تحتاجه للتشخيص: اسم المجموعة
(store.users)، اسم الفهرس المخالَف (email_1)، والقيمة المكرَّرة نفسها.
لماذا يحدث أصلًا؟
الخطأ ناتج عن فهرس فريد (unique index) — أنشأته أنت أو مكتبة مثل Mongoose بناءً على تعريف المخطّط:
db.users.createIndex({ email: 1 }, { unique: true })
بمجرّد وجود هذا الفهرس، أي محاولة إدراج أو تحديث تنتج عنها قيمتان
متطابقتان في حقل email تُرفَض فورًا — هذا هو الغرض من الفهرس أصلًا،
وليس عطلًا في MongoDB.
السبب الأول: تكرار فعلي في البيانات
الحالة الأبسط: المستخدم يحاول التسجيل ببريد مسجَّل مسبقًا فعلًا.
try {
await db.users.insertOne({ email: "user@example.com", name: "أحمد" });
} catch (err) {
if (err.code === 11000) {
// تعامل معه كخطأ منطقي متوقَّع، لا استثناءً يوقف التطبيق
throw new Error("هذا البريد الإلكتروني مسجّل مسبقًا");
}
throw err;
}
💡 كود الخطأ الرقمي دائمًا
11000— تحقّق منه فيcatchبدل مقارنة نص الرسالة، لأن النص قد يختلف بين إصدارات السائق (driver).
إن كان قصدك فعليًا «حدّث إن وُجد، أنشئ إن لم يوجد»، upsert يزيل الحاجة لهذا التحقّق من الأساس:
await db.users.updateOne(
{ email: "user@example.com" },
{ $set: { lastLogin: new Date() } },
{ upsert: true }
);
السبب الثاني: فهرس قديم نسيته بعد تعديل المخطّط
شائع جدًا في مشاريع قيد التطوير: أنشأت فهرسًا unique مبكّرًا، ثم
غيّرت رأيك في منطق التطبيق، لكن الفهرس نفسه بقي في قاعدة البيانات —
لأن حذف/تعديل الفهارس لا يحدث تلقائيًا عند تعديل كود المخطّط
(حتى لو كنت تستخدم Mongoose).
// اعرض كل الفهارس الفعلية على المجموعة أولًا
db.users.getIndexes()
// احذف الفهرس القديم غير المرغوب فيه بعد التأكد
db.users.dropIndex("username_1")
السبب الثالث: تكرار قيمة null أو حقل مفقود
الحالة الأقل توقّعًا: فهرس unique عادي (غير sparse) يعامل الحقل
المفقود بالكامل كـ null، ويسمح بمستند واحد فقط بهذه القيمة على
كامل المجموعة. أي مستند ثانٍ بلا هذا الحقل يُرفَض بنفس رسالة E11000:
db.users.createIndex({ username: 1 }, { unique: true })
db.users.insertOne({ email: "a@x.com" }) // ✅ ينجح، username مفقود = null
db.users.insertOne({ email: "b@x.com" }) // ❌ E11000 — null مكرَّر
الحل: استخدم فهرسًا sparse إن كان الحقل اختياريًا فعلًا، حتى لا
تُدرَج المستندات الخالية منه في الفهرس أصلًا:
db.users.createIndex({ username: 1 }, { unique: true, sparse: true })
جدول تشخيص سريع
| العلامة | السبب المرجّح | الحل |
|---|---|---|
| القيمة نفسها موجودة فعلًا في مستند آخر | تكرار بيانات حقيقي | تحقّق قبل الإدراج، أو استخدم upsert |
| الرسالة تذكر فهرسًا لا تتذكّره في الكود الحالي | فهرس قديم من مخطّط سابق | getIndexes() ثم dropIndex() |
القيمة المكرَّرة في الرسالة هي null | حقل اختياري بفهرس unique عادي | أضف sparse: true للفهرس |
الخلاصة
E11000 ليست رسالة عطل غامضة — هي فهرس unique يقوم بعمله بالضبط.
اقرأ اسم الفهرس والقيمة من نص الرسالة، ثم صنّف السبب بين الثلاثة أعلاه
قبل أن تلمس أي كود. وإن كان المنطق المقصود فعليًا "أنشئ أو حدّث"، صمّم
الكتابة بـ upsert من البداية بدل معالجة الخطأ لاحقًا.
راجع درس الفهارس في مسار MongoDB لأساسيات
الفهرسة، ومرجع دوال MongoDB لصياغة updateOne
وfindOneAndUpdate كاملة، وإن كنت لا تزال تقارن بين الخيارات فراجع
الفرق بين MongoDB و PostgreSQL.