MongoDB: الوثائق وBSON: حجر الأساس لبيانات MongoDB

آخر تحديث: 2026-08-26

BSON هو تنسيق البيانات الخاص بـ MongoDB — وهو يوسع نطاق إمكانيات JSON ويدعم الأنواع الأصلية مثل Date و Binary و Decimal128.

تقدم هذه الدورة فهمًا متعمقًا لتنسيق البيانات BSON، والبنية الداخلية لـ ObjectId، ونظام أنواع الحقول، كما تعلّم أفضل الممارسات لتصميم المستندات.

1. ما ستتعلمه



2. قصة حقيقية لمهندس متكامل

(1) المشكلة: يتم تحويل التواريخ إلى سلاسل نصية عند تخزين JSON في MongoDB

تشارلي هو مهندس متكامل في Node.js يعمل حالياً على ترحيل بيانات MySQL إلى MongoDB:

"قمت بتحويل بيانات الطلبات من MySQL إلى JSON وتخزينها في MongoDB، لأكتشف بعد ذلك أن جميع التواريخ قد تم تحويلها إلى السلسلة new Date()، والتي تعذر تحليلها؛ كما فقدت دقة المبالغ (0.1 + 0.2 ≠ 0.3)؛ ولم يكن بالإمكان تخزين صور الملف الشخصي الثنائية على الإطلاق."

قام بتسلسل بيانات الطلبات باستخدام JSON.stringify()، مما أدى إلى فقدان معلومات النوع:

JAVASCRIPT
// ❌ Error:JSON.stringify Type of Loss
const order = {
  createdAt: new Date(),         // Date Object
  total: new Number('0.30'),     // Decimal128 A more precise type should be used.
  avatar: Buffer.from('...'),    // Binary Avatar
  _id: new ObjectId()            // MongoDB Expected ObjectId
};

const json = JSON.stringify(order);
// {"createdAt":"2026-07-01T...","total":0.3,"avatar":"...","_id":"..."}
//         ^^^^^^^^^^^^^^^^ String        ^ Floating-point numbers(Loss of Accuracy) ^ String(Cannot be restored)

(2) حل BSON

يقوم MongoDB بتخزين البيانات مباشرةً بتنسيق BSON (JSON الثنائي)، مع الحفاظ على جميع معلومات الأنواع.

JAVASCRIPT
// ✅ Correct:mongoose Direct Operation BSON Type
const OrderSchema = new mongoose.Schema({
  createdAt: { type: Date, default: Date.now },           // BSON Date
  total: { type: mongoose.Schema.Types.Decimal128 },        // BSON Decimal128(Accurate)
  avatar: { type: Buffer },                                 // BSON Binary
  _id: { type: mongoose.Schema.Types.ObjectId, auto: true } // BSON ObjectId
});

const order = await Order.create({
  total: mongoose.Types.Decimal128.fromString('0.30'),
  // TODO: 替换为实际头像文件路径
      avatar: fs.readFileSync('avatar.jpg')
});

(3) الإيرادات

البعد JSON BSON
نوع التاريخ سلسلة (تتطلب تحليلًا يدويًّا) تاريخ أصلي (بدقة الميلي ثانية)
الدقة العددية النقطة العائمة (فقدان الدقة) Decimal128 (دقة 34 بت)
البيانات الثنائية غير مدعومة ثنائية أصلية
ترتيب الحقول غير مرتب مرتب (مهم!)
الحجم واستخدام الموارد أصغر حجمًا أكبر قليلاً (أكبر بنسبة 5–15٪)


3. تنسيق البيانات BSON

نظرة عامة على المفهوم: BSON (Binary JSON) هو تنسيق تسلسل ثنائي خاص بـ MongoDB، ويُعد مجموعة شاملة لـ JSON. في حين أن JSON لا يحتوي إلا على ستة أنواع من البيانات (سلسلة، رقم، منطقية، فارغة، مصفوفة، وكائن)، فإن BSON يدعم أكثر من 12 نوعًا، بما في ذلك الأنواع الأساسية لقاعدة البيانات مثل Date و Binary و ObjectId و Decimal128. وتتمثل المزايا الأساسية لـ BSON في: أنواع البيانات الغنية، والحقول المرتبة، والتحليل السريع للغاية.

كيفية العمل: تُخزَّن مستندات BSON بتنسيق ثنائي. يبدأ كل مستند برأس طول مكون من 4 بايت، يتبعه تسلسل من أزواج المفتاح-القيمة، وينتهي بـ 0x00. وعلى عكس عملية تحليل JSON القائمة على النص، تتيح رأس الطول في BSON تخطي الحقول غير الضرورية بسرعة (على غرار تصميم «رأس الطول الثابت» في البروتوكولات الثنائية)، مما يؤدي إلى أداء تحليل أسرع بـ 3–5 مرات من JSON. والمقابل لذلك هو زيادة بنسبة 5–15% في الحجم الإضافي المطلوب (لتخزين معلومات النوع والطول).

100%
graph TB
    subgraph "BSON Internal Structure of the Document"
        A[4 Byte<br/>Total length of the document] --> B[Type Code 1B<br/>+ Field Name<br/>+ Value]
        B --> C[Type Code 1B<br/>+ Field Name<br/>+ Value]
        C --> D[...More key-value pairs...]
        D --> E[0x00<br/>Closing tag]
    end
    
    style A fill:#cce5ff
البعد JSON BSON
النوع التنسيق النصي التنسيق الثنائي
سهولة القراءة ✅ قابلة للقراءة البشرية ❌ ثنائية
الأداء دقة منخفضة دقة فائقة السرعة (3–5x)
مجموعة متنوعة 6 أنواع 12+ نوعًا
ترتيب حقل غير مرتب مرتب
المساحة أكثر إحكاما أكبر بنسبة 5–15%

(1) ما هو BSON؟

BSON (Binary JSON) هو تنسيق التسلسل الثنائي الذي تستخدمه قاعدة بيانات MongoDB. وتشمل ميزاته ما يلي:

100%
graph LR
    A[JavaScript Object] -->|JSON.stringify| B[JSON Text]
    A -->|BSON Serialization| C[BSON Binary]

    B --> D[Transmission / Storage]
    C --> D

    style C fill:#d4edda
البعد JSON BSON
النوع التنسيق النصي التنسيق الثنائي
سهولة القراءة ✅ قابلة للقراءة البشرية ❌ ثنائية
الأداء دقة منخفضة دقة فائقة السرعة
مجموعة متنوعة 6 أنواع 12+ نوعًا
ترتيب حقل غير مرتب مرتب
المساحة أكثر إحكاما أكبر بنسبة 5–15%

(2) بنية مستند BSON

تحليل النقاط الرئيسية:

  1. ترتيب إدراج الحقول في BSON — وهذا أمر بالغ الأهمية لفهرسة البيانات وتحسين الاستعلامات في MongoDB
  2. يسبق كل حقل رمز نوع مكون من بايت واحد، مما يتيح لـ BSON التمييز بين «التاريخ» و«السلسلة» (على عكس JSON).
  3. يتم تخزين المستندات والمصفوفات المتداخلة بشكل متكرر في BSON، بحد أقصى لعمق التداخل يبلغ 100 مستوى.
  4. يظهر الحقل _id دائمًا في بداية المستند، مما يساهم في تحسين أداء الاستعلام
JAVASCRIPT
// One BSON Internal Representation of a Document(Simplify)
{
  _id: ObjectId("507f1f77bcf86cd799439011"),    // 12 Byte ObjectId
  name: "Alice",                                // String(UTF-8)
  age: 28,                                      // Int32
  balance: Decimal128("12345.6789"),            // Decimal128(High precision)
  joinedAt: ISODate("2026-07-01T10:00:00Z"),    // Date(64-bit Integer)
  isActive: true,                               // Boolean
  hobbies: ["reading", "coding", "hiking"],     // Array
  address: {                                    // Embedded Document
    city: "Tokyo",
    country: "Japan"
  },
  profile: null,                                // Null
  avatar: BinData(0, "..."),                    // Binary
  // Field Order:BSON Preserve the insertion order of fields(JSON No guarantee)
}

▶ المثال 1: عرض تفاصيل BSON في mongosh

JAVASCRIPT
// Insert a document
db.users.insertOne({
  name: "Alice",
  age: 28,
  joinedAt: new Date(),
  balance: NumberDecimal("12345.6789"),
  address: { city: "Tokyo", country: "Japan" }
});

// View BSON Details(Usage bsonSon Function)
db.users.findOne({ name: "Alice" });

الإخراج:

TEXT 📖 للعرض فقط
{
  _id: ObjectId('507f1f77bcf86cd799439011'),
  name: 'Alice',
  age: 28,
  joinedAt: ISODate('2026-07-01T10:00:00.000Z'),
  balance: NumberDecimal('12345.6789'),
  address: { city: 'Tokyo', country: 'Japan' }
}
JAVASCRIPT

// View Field Types
const doc = db.users.findOne({ name: "Alice" });
print(typeof doc.age);             // number
print(doc.joinedAt instanceof Date); // true

الإخراج:

TEXT 📖 للعرض فقط
number
true


4. آلية المفتاح الأساسي ObjectId

شرح المفهوم: ObjectId هو نوع المفتاح الأساسي الافتراضي في MongoDB، ويتألف من قيمة ثنائية مكونة من 12 بايت (96 بت). وعلى عكس المفاتيح الأساسية الصحيحة التي تتزايد تلقائيًا والموجودة في قواعد البيانات التقليدية، يستخدم ObjectId تصميمًا موزعًا — يتألف من طابع زمني وقيمة عشوائية وعداد — مما يضمن التفرّد العالمي دون الحاجة إلى تنسيق مركزي. ومن المزايا الرئيسية الأخرى لـ ObjectId أنه يتضمن بطبيعته وقت الإنشاء، والذي يمكن استخراجه مباشرةً دون الحاجة إلى حقول إضافية.

كيفية العمل: تنقسم الـ 12 بايت التي يتكون منها ObjectId إلى ثلاثة أجزاء: أول 4 بايت هي طابع زمني بنظام Unix (بدقة تصل إلى الثانية)، والـ 5 بايت الوسطى هي قيمة عشوائية (يتم تحديدها بواسطة معرف الجهاز ومعرف العملية عند إنشائها لأول مرة، وتبقى دون تغيير بعد ذلك)، والـ 3 بايت الأخيرة هي عداد متزايد (يتزايد من قيمة بداية عشوائية ضمن نفس الثانية). يسمح هذا التصميم لعملية واحدة بإنشاء ما يقارب 16.77 مليون معرّف ObjectId فريد خلال ثانية واحدة.

100%
graph LR
    A[ObjectId 12 Byte] --> B[4 Byte Timestamp<br/>Accuracy to the second]
    A --> C[5 Random Byte Values<br/>Machine/Unique Process]
    A --> D[3 Byte-Increment Counter<br/>Unique within a single second]

    style A fill:#cce5ff
القسم الطول المحتوى الغرض
الطابع الزمني 4 بايت الطابع الزمني لنظام يونكس (بالثواني) يمكن استخراج وقت الإنشاء
عشوائي 5 بايت معرّف الجهاز + معرّف العملية فريد عبر جميع العمليات
العداد 3 بايت عداد تزايدي فريد خلال ثانية واحدة
_id الاستراتيجية المزايا العيوب حالات الاستخدام
معرّف الكائن (ObjectId) الذي يتم إنشاؤه تلقائيًا موزّع، فريد، مختوم بختم زمني، مرتب ترتيبًا طبيعيًّا 12 بايت (كبير نسبيًّا) للأغراض العامة (افتراضي)
مفاتيح الأعمال النصية دلالات واضحة، سهولة قراءة جيدة يجب التأكد يدويًّا من تفردها رقم الطلب، SKU
الأعداد الصحيحة ذات التزايد التلقائي موجزة وسهلة القراءة تتطلب تعيين عداد؛ غير مناسبة لتقسيم البيانات الأنظمة القديمة
UUID فريد عالميًا 16 بايت، غير مرتبة فريد عبر الأنظمة

(1) ما هو ObjectId؟

ObjectId هو نوع المفتاح الأساسي الافتراضي في MongoDB، وهو قيمة ثنائية مكونة من 12 بايت (96 بت):

100%
graph LR
    A[ObjectId 12 Byte] --> B[4 Byte Timestamp<br/>Accuracy to the second]
    A --> C[5 Random Byte Values<br/>Machine/Unique Process]
    A --> D[3 Byte-Increment Counter<br/>Unique within a single second]

    style A fill:#cce5ff
القسم الطول المحتوى الغرض
الطابع الزمني 4 بايت الطابع الزمني لنظام يونكس (بالثواني) يمكن استخراج وقت الإنشاء
عشوائي 5 بايت معرّف الجهاز + معرّف العملية فريد عبر جميع العمليات
العداد 3 بايت عداد تزايدي فريد خلال ثانية واحدة

(2) مزايا ObjectId

تحليل النقاط الرئيسية:

  1. يقوم جزء الطابع الزمني في ObjectId تلقائيًّا بفرز الإدخالات حسب وقت الإدراج — مما يتيح إجراء الاستعلامات حسب النطاق الزمني دون الحاجة إلى فهارس createdAt إضافية.
  2. يتم إنشاء قيمة عشوائية مكونة من 5 بايت وتخزينها مؤقتًا عند بدء العملية، مما يضمن تفردها عبر العمليات المختلفة (2^40 ≈ 1 تريليون احتمال).
  3. يزداد عداد الـ 3 بايت بمقدار واحد في غضون ثانية واحدة، مما ينتج عنه 2^24 ≈ 16.77 مليون معرّف فريد في الثانية.
  4. يمكن لطريقة getTimestamp() استخراج وقت الإنشاء مباشرةً من ObjectId دون الحاجة إلى استعلامات إضافية.
JAVASCRIPT
// Create ObjectId in mongosh
const id1 = ObjectId();              // Automatically Generated
const id2 = ObjectId("507f1f77bcf86cd799439011");  // Generate from a string

// Retrieve the creation time(Key Advantages!)
id2.getTimestamp();

الإخراج:

TEXT 📖 للعرض فقط
ISODate("2012-10-17T20:46:11.000Z")
JAVASCRIPT

// Use in Node.js with mongoose
const mongoose = require('mongoose');
const id = new mongoose.Types.ObjectId();
console.log(id.getTimestamp());  // 2026-07-01T10:00:00.000Z

(3) ضمان تفرد معرّف الكائن (ObjectId)

100%
graph TB
    A[Client A<br/>Generated in the same second ID] --> A1[time=1000<br/>random=ABC<br/>counter=1]
    A --> A2[time=1000<br/>random=ABC<br/>counter=2]
    A --> A3[time=1000<br/>random=ABC<br/>counter=3]

    B[Client B<br/>Generated in the same second ID] --> B1[time=1000<br/>random=DEF<br/>counter=1]
    B --> B2[time=1000<br/>random=DEF<br/>counter=2]

    style A1 fill:#d4edda
    style B1 fill:#d4edda

▶ المثال 2: استخراج الطابع الزمني لـ ObjectId

JAVASCRIPT
// === In mongosh ===
const products = db.products.find().toArray();
products.forEach(p => {
  print(`Product ${p._id} created at ${p._id.getTimestamp()}`);
});

// === by ObjectId Time Range Query ===
const startOfDay = ObjectId.createFromTime(
  Math.floor(new Date('2026-07-01').getTime() / 1000)
);
const endOfDay = ObjectId.createFromTime(
  Math.floor(new Date('2026-07-02').getTime() / 1000)
);

db.products.find({
  _id: { $gte: startOfDay, $lt: endOfDay }
});

// === Node.js / mongoose ===
const Product = mongoose.model('Product', productSchema);
const products = await Product.find({
  _id: {
    $gte: mongoose.Types.ObjectId.createFromTime(
      Math.floor(Date.parse('2026-07-01') / 1000)
    ),
    $lt: mongoose.Types.ObjectId.createFromTime(
      Math.floor(Date.parse('2026-07-02') / 1000)
    )
  }
});

الإخراج:

TEXT 📖 للعرض فقط
Product ObjectId('507f1f77bcf86cd799439011') created at ISODate('2012-10-17T20:46:11.000Z')
Product ObjectId('507f1f77bcf86cd799439012') created at ISODate('2012-10-17T20:46:12.000Z')


5. أنواع بيانات BSON

شرح المفهوم: يدعم BSON أكثر من 12 نوعًا من أنواع البيانات، وهو ما يتجاوز بكثير أنواع البيانات الستة التي يدعمها JSON. ويكمن الاختلاف الأهم في الأنواع الرقمية — حيث لا يحتوي JSON إلا على نوع رقمي واحد (Number، وهو رقم عائم بدقة مزدوجة وفقًا لمعيار IEEE 754)، بينما يوفر BSON أربعة أنواع رقمية: Double، وInt32، وInt64 (Long)، وDecimal128. قد يؤدي اختيار النوع العددي الخاطئ إلى فقدان الدقة (على سبيل المثال، في الحسابات النقدية 0.1 + 0.2 ≠ 0.3).

حالات الاستخدام: يجب استخدام Decimal128 (دقة عشرية مكونة من 34 رقمًا) للمبالغ المالية؛ ويستخدم Int32 للعدادات؛ وLong للمعرّفات الصحيحة الكبيرة؛ بينما تستخدم Double للحسابات العلمية والإحصاءات. يُخزَّن النوع Date في BSON كطابع زمني من 64 بت بالمللي ثانية، وهو ما يختلف اختلافًا جوهريًّا عن التواريخ المستندة إلى السلاسل في JSON.

النوع رمز النوع مثال الغرض
مزدوج 1 3.14، 0.1+0.2 عائم (الرقم الافتراضي)
سلسلة 2 "Alice" سلسلة UTF-8
الكائن 3 { key: "value" } مستند متداخل
مصفوفة 4 [1, 2, 3] مصفوفة
البيانات الثنائية 5 BinData(0, "...") البيانات الثنائية (الصور، الملفات)
غير محدد 6 undefined غير موصى به
ObjectId 7 ObjectId("...") المفتاح الأساسي الافتراضي
منطقية 8 true، false منطقية
التاريخ 9 ISODate("...") التاريخ والوقت
Null 10 null قيمة فارغة
التعبير النمطي 11 /pattern/i التعبير النمطي
عدد صحيح 32 بت 16 NumberInt(123) عدد صحيح 32 بت
عدد صحيح 64 بت 18 NumberLong(123) عدد صحيح 64 بت (BigInt)
Decimal128 19 NumberDecimal("0.30") أرقام عشرية عالية الدقة (المجال المالي)
MinKey/MaxKey -1 / 127 MinKey()، MaxKey() حدود المقارنة

(1) 12 نوعًا من أنواع بيانات BSON

(2) اختيار نوع رقمي

شرح المفهوم: يُعد اختيار نوع البيانات الرقمية القرار الأكثر أهمية عند التعامل مع أنواع بيانات BSON. لا يحتوي JSON سوى على نوع واحد للرقم (نقطة عائمة مزدوجة الدقة)، مما يؤدي إلى المشكلة الكلاسيكية المتمثلة في فقدان الدقة في الحسابات المالية: 0.1 + 0.2 = 0.30000000000000004. يحل نوع Decimal128 في BSON هذه المشكلة من خلال توفير دقة عشرية تبلغ 34 بت، مما يجعله مناسبًا للسيناريوهات التي تتطلب حسابات دقيقة، مثل المبالغ ومعدلات الضرائب.

النوع الرقمي الدقة النطاق حجم التخزين حالات الاستخدام
مزدوج 15–17 رقمًا معنويًا ±1.7×10^308 8 بايت الحوسبة العلمية، والإحصاء، والرسومات
Int32 دقيق -2^31 ~ 2^31-1 4 بايت العد للأغراض العامة، الجرد
Int64/Long الدقة -2^63 ~ 2^63-1 8 بايت معرّفات الأعداد الصحيحة الكبيرة، الطوابع الزمنية
Decimal128 عشري 34 بت ±10^6145 16 بايت المبلغ المالي (موصى به)
100%
graph TB
    A[MongoDB Numeric Types] --> B[Double<br/>Default]
    A --> C[Int32<br/>32 Integer]
    A --> D[Long<br/>64 Integer]
    A --> E[Decimal128<br/>34 Decimal place]

    B --> B1[Applicable:Scientific Computing、Statistics]
    C --> C1[Applicable:Routine Count]
    D --> D1[Applicable:Large integers ID]
    E --> E1[Applicable:Finance、Amount]

    style E fill:#d4edda

▶ المثال 3: التعامل مع الأنواع العددية

JAVASCRIPT
// === Double(Default)===
db.products.insertOne({
  sku: "PHONE-001",
  price: 599.99  // Save as Double
});

// === Decimal128(Financial Recommendations)===
db.accounts.insertOne({
  balance: NumberDecimal("1234567890.12345678901234567890")
  // Precise Storage,No loss of precision
});

// === Int32(Count)===
db.products.insertOne({
  sku: "BOOK-001",
  stock: NumberInt(150)
});

// === Long(Large integers ID)===
db.orders.insertOne({
  _id: NumberLong("1700000000000")  // Timestamps as ID
});

// === JavaScript Processing Decimal128 ===
const account = await Account.findOne({});
console.log(account.balance.toString());  // "1234567890.12345678901234567890"

// === Number The Precision Trap ===
0.1 + 0.2;                          // 0.30000000000000004 ❌
NumberDecimal("0.1") + NumberDecimal("0.2"); // NumberDecimal("0.3") ✅

الإخراج:

TEXT 📖 للعرض فقط
0.30000000000000004
NumberDecimal("0.3")


6. قواعد تسمية الحقول

شرح المفهوم: قد يبدو تسمية الحقول تفصيلًا ثانويًّا، لكنها تؤثر بشكل كبير على التعاون بين أعضاء الفريق والصيانة على المدى الطويل. يفرض MongoDB ثلاثة قيود صارمة على أسماء الحقول (لا يمكن أن تبدأ بـ $، ولا يمكن أن تحتوي على .، ولا يمكن أن تكون سلسلة فارغة)، بالإضافة إلى عدة توصيات غير إلزامية (يُنصح باستخدام أسلوب camelCase، وتجنب الكلمات المحجوزة، والحد من الطول). وتعد قواعد التسمية المتسقة أساس قابلية صيانة قاعدة البيانات.

سيناريوهات الاستخدام: يوصي نظام JavaScript/TypeScript باستخدام أسلوب camelCase (بما يتوافق مع أسماء المتغيرات في الكود)، بينما يوصي نظام Python/SQL باستخدام أسلوب snake_case (بما يتوافق مع أسماء أعمدة قاعدة البيانات). وفي مجموعة التقنيات MongoDB + Mongoose، يُوصى باستخدام أسلوب camelCase لأسماء حقول قاعدة البيانات، مع إخراجها بأسلوب snake_case في طبقة واجهة برمجة التطبيقات (API) عبر تحويل toJSON الخاص بـ Mongoose.

أسلوب التسمية مثال المزايا العيوب التوصية
camelCase firstName دعم أصلي في JS/TS غير متوافق مع SQL ⭐⭐⭐ (موصى به)
snake_case first_name متوافق مع SQL/Python يحتاج إلى علامات اقتباس في JS ⭐⭐
kebab-case first-name متوافق مع عناوين URL يتطلب استخدام علامات الاقتباس في MongoDB

(1) قواعد تسمية الحقول في MongoDB

التسمية الصحيحة:

JAVASCRIPT
// ✅ Valid field names
db.users.insertOne({
  firstName: "Alice",          // Hump-style
  first_name: "Alice",         // Snake-like
  "first-name": "Alice",       // kebab-case(Quotation marks are required)
  "user 1": "Alice",           // Contains spaces(Quotation marks are required)
  age28: 28                     // Ending in a number
});

// ❌ Invalid field name
db.users.insertOne({
  $name: "Alice",              // starts with $ ❌
  "user.name": "Alice",        // contains . ❌
  "": "Alice"                  // Empty string ❌
});

(2) مقارنة بين ثلاث قواعد لتسمية العناصر

النمط مثال المزايا العيوب
camelCase firstName دعم أصلي في JS/TS غير متوافق مع SQL
snake_case first_name متوافق مع SQL/Python يحتاج إلى علامات اقتباس في JS
kebab-case first-name متوافق مع عناوين URL يتطلب استخدام علامات الاقتباس في MongoDB

(3) التوصية: camelCase + الأسلوب الرسمي لـ MongoDB

JAVASCRIPT
// ✅ Recommended Styles:camelCase
db.users.insertOne({
  firstName: "Alice",
  lastName: "Smith",
  emailAddress: "alice@example.com",
  dateOfBirth: new Date("1998-01-01"),
  isActive: true,
  totalSpent: NumberDecimal("1234.56")
});

▶ المثال 4: قواعد تسمية مخططات Mongoose

JAVASCRIPT
// mongoose Automatically convert camelCase to database fields
const UserSchema = new mongoose.Schema({
  firstName: { type: String, required: true },       // Database Fields:firstName
  emailAddress: { type: String, required: true },   // Database Fields:emailAddress
  createdAt: { type: Date, default: Date.now },     // Database Fields:createdAt
  isActive: { type: Boolean, default: true }        // Database Fields:isActive
});

// Through toJSON Convert Underscore-Based Naming Conventions(API On the way back)
UserSchema.set('toJSON', {
  virtuals: true,
  versionKey: false,
  transform: (doc, ret) => {
    ret.first_name = ret.firstName;
    delete ret.firstName;
    return ret;
  }
});


7. حدود حجم المستندات

شرح المفهوم: يبلغ الحد الأقصى لحجم المستند الفردي في MongoDB 16 ميغابايت، ويبلغ الحد الأقصى لعمق التداخل 100 مستوى. ويُعد هذا القيد إحدى الفلسفات التصميمية الأساسية لـ MongoDB — فهو يشجع على تضمين البيانات ذات الصلة داخل مستند واحد (لتجنب عمليات JOIN)، ولكنه لا يشجع على تخزين مستندات ضخمة للغاية. ويسمح الحد الأقصى البالغ 16 ميغابايت لـ MongoDB بمعالجة المستندات الفردية بكفاءة في الذاكرة، مما يضمن أوقات استجابة سريعة للاستعلامات والتحديثات.

كيفية العمل: السبب الأساسي وراء حد الـ 16 ميغابايت هو أن محرك التخزين WiredTiger في MongoDB يستخدم استراتيجية «التحديث في المكان نفسه» عند تعديل المستندات — فإذا زاد حجم المستند بعد التحديث ولم تكن المساحة كافية في موقعه الأصلي، يجب نقل المستند إلى موقع جديد، مما يؤدي إلى تحديث الفهرس (يجب تحديث جميع إدخالات الفهرس التي تشير إلى ذلك المستند). وكلما زاد حجم المستند، زادت تكلفة نقله. ولذلك، اختارت MongoDB 16 ميغابايت كنقطة توازن.

البعد القيد السبب
حجم المستند الواحد 16 ميغابايت الحد الأقصى لحجم مستند BSON
عمق التداخل 100 مستوى (الافتراضي) يمنع تجاوز سعة المكدس
طول اسم الحقل 255 بايت ترميز UTF-8
عدد الفهارس 64 لكل مجموعة حجم بيانات تعريف الفهرس
الطول الإجمالي لمفتاح فهرس مجمَّع واحد 1024 بايت كفاءة الفهرس
سيناريوهات تجاوز الحدود الحلول الوصف
الملفات الكبيرة (الصور/مقاطع الفيديو) GridFS التخزين على شكل كتل، 255 كيلوبايت لكل كتلة
نص طويل جدًّا Elasticsearch + المراجع المستندات تخزن المعرّفات، بينما يخزن ES النص الكامل
المصفوفة كبيرة جدًا (قائمة التعليقات) تقسيمها إلى مجموعات منفصلة مجموعة التعليقات + المرجع
التسلسل الهرمي المفرط التصميم المسطح تقليل مستويات التسلسل الهرمي

(1) الحد الأقصى 16 ميغابايت

(2) لماذا 16 ميغابايت؟

فلسفة تصميم MongoDB: تجنب تخزين المستندات الضخمة:

(3) حلول لسيناريوهات المستندات الكبيرة

100%
graph TB
    A[Large-Document Scenarios] --> B[Binary file<br/>Image/Video]
    A --> C[Long Text<br/>Article/Log]
    A --> D[The array is too large<br/>List of Comments]

    B --> E[GridFS<br/>Block Storage]
    C --> F[Text Search<br/>Elasticsearch]
    D --> G[Split Set<br/>comments Gathering]

    style E fill:#d4edda
    style F fill:#d4edda
    style G fill:#d4edda

▶ المثال 5: تخزين الملفات الكبيرة في GridFS

JAVASCRIPT
// === Storing Large Files(>16MB)===
const mongoose = require('mongoose');
const Grid = require('gridfs-stream');
const fs = require('fs');

const conn = mongoose.connection;
let gfs;
conn.once('open', () => {
  gfs = Grid(conn.db, mongoose.mongo);
  gfs.collection('uploads');
});

// Upload File
const writestream = gfs.createWriteStream({
  filename: 'large-video.mp4',
  content_type: 'video/mp4'
});

fs.createReadStream('./local-video.mp4').pipe(writestream);

writestream.on('close', (file) => {
  console.log(`File stored: ${file._id}`);
});

// Download File
const readstream = gfs.createReadStream({
  _id: ObjectId('507f1f77bcf86cd799439011')
});

readstream.pipe(fs.createWriteStream('./downloaded-video.mp4'));

الإخراج:

TEXT 📖 للعرض فقط
File stored: ObjectId('507f1f77bcf86cd799439011')


8. التوثيق المدمج مقابل الاقتباسات

شرح المفهوم: توجد استراتيجيتان رئيسيتان لنمذجة العلاقات بين المستندات في MongoDB — الاستراتيجية المدمجة (Embed) والاستراتيجية المرجعية (Reference). تقوم الاستراتيجية المضمنة بتضمين البيانات ذات الصلة مباشرةً داخل المستند الأصلي، مما يتيح استرجاع جميع البيانات في استعلام واحد؛ أما الاستراتيجية المرجعية فتخزن البيانات ذات الصلة في مجموعات منفصلة، يتم الوصول إليها عبر مراجع ObjectId، مما يتطلب استعلامات متعددة باستخدام $lookup أو على مستوى طبقة التطبيق. ويُعد الاختيار بين هاتين الاستراتيجيتين القرار الأكثر أهمية في نمذجة البيانات في MongoDB.

كيفية العمل: يتم تخزين المستندات المضمنة والمستندات الأم في نفس مستند BSON وتشتركان في نفس دورة الحياة — فعند تحديث المستند الأم، يتم استبدال المستند المضمن أيضًا، وعند الاستعلام عن المستند الأم، يتم إرجاع المستند المضمن معه. أما المستندات المشار إليها فهي مستندات BSON مستقلة لها _id ودورة حياة خاصة بها؛ ولا تؤثر التحديثات التي تُجرى على أحدهما على الآخر، لكن الاستعلام عنها يتطلب عملية ربط إضافية.

100%
graph TB
    A[Document Relationship Modeling] --> B{Data Characteristics}
    B -->|1:1 Relationship<br/>Small data set<br/>We often read together| C[Embedded ✅<br/>Retrieve in a single query]
    B -->|1:N Relationship<br/>N Smaller<br/>It is rarely checked on its own.| D[Embedded ✅<br/>Nested Arrays]
    B -->|1:N Relationship<br/>N Larger<br/>Needs to be checked separately| E[Quotation Style ✅<br/>Independent Set]
    B -->|N:N Relationship| F[Quotation Style ✅<br/>Two-way ID Array]
    B -->|Frequent Updates to Subdocuments| G[Quotation Style ✅<br/>Avoid rewriting the entire document]

    style C fill:#d4edda
    style D fill:#d4edda
    style E fill:#d4edda
السيناريو التوصية السبب
علاقة 1:1 (المستخدم-العنوان) مدمجة (ما لم يتغير العنوان بشكل متكرر) استرجاع جميع البيانات في استعلام واحد
علاقة 1:N (المستخدم - الطلب) يعتمد على قيمة N:
صغير → مدمج؛ كبير → مرجعي
الحد الأقصى لحجم المستند
علاقة N:N (المستخدم-الدور) مرجع (مصفوفة معرّفات ثنائية الاتجاه) علاقة معقدة
التحديثات المتكررة للوثائق الفرعية مرجع تجنب إعادة كتابة الوثيقة بأكملها
يتطلب إجراء استعلامات منفصلة للمستندات الفرعية مرجع أداء الاستعلامات المنفصلة

(1) استراتيجيتان لنمذجة العلاقات

100%
graph TB
    subgraph "Embedded Documentation(Embed)"
        A1[users Gathering] --> A2[Document 1<br/>address: {<br/>  city: Tokyo<br/>  country: Japan<br/>}]
    end

    subgraph "Citation-Style Documentation(Reference)"
        B1[users Gathering] --> B2[Document 1<br/>address_id: ObjectId]
        B3[addresses Gathering] --> B4[Document 1<br/>city: Tokyo]
        B2 -.->|Search| B3
    end

(2) اختر استراتيجية

السيناريو التوصية السبب
علاقة 1:1 (المستخدم-العنوان) مدمجة (ما لم يتغير العنوان بشكل متكرر) استرجاع جميع البيانات في استعلام واحد
علاقة 1:N (المستخدم - الطلب) يعتمد على قيمة N:
صغير → مدمج؛ كبير → مرجعي
الحد الأقصى لحجم المستند
علاقة N:N (المستخدم-الدور) مرجع (مصفوفة معرّفات ثنائية الاتجاه) علاقة معقدة
التحديثات المتكررة للوثائق الفرعية مرجع تجنب إعادة كتابة الوثيقة بأكملها
يتطلب إجراء استعلامات منفصلة للمستندات الفرعية مرجع أداء الاستعلامات المنفصلة

(3) مثال على مستند مضمن

JAVASCRIPT
// === Embedded:User + Multiple Addresses ===
db.users.insertOne({
  _id: ObjectId("507f1f77bcf86cd799439011"),
  name: "Alice",
  email: "alice@example.com",
  addresses: [                          // Nested Arrays
    {
      type: "home",
      street: "123 Main St",
      city: "Tokyo",
      country: "Japan",
      zip: "100-0001"
    },
    {
      type: "work",
      street: "456 Office Rd",
      city: "Tokyo",
      country: "Japan",
      zip: "100-0002"
    }
  ]
});

// === Search:Living in Tokyo users ===
db.users.find({ "addresses.city": "Tokyo" });

▶ المثال 6: مثال على وثيقة مكتوبة بأسلوب الاقتباس

JAVASCRIPT
// === Quotation Style:User + Order(Many-to-one) ===
// users Gathering
db.users.insertOne({
  _id: ObjectId("507f1f77bcf86cd799439011"),
  name: "Alice",
  email: "alice@example.com"
});

// orders Gathering
db.orders.insertMany([
  {
    _id: ObjectId("507f1f77bcf86cd799439012"),
    user_id: ObjectId("507f1f77bcf86cd799439011"),  // Quote
    items: ["PHONE-001", "CASE-002"],
    total: NumberDecimal("649.98"),
    createdAt: new Date()
  },
  {
    _id: ObjectId("507f1f77bcf86cd799439013"),
    user_id: ObjectId("507f1f77bcf86cd799439011"),  // Quote
    items: ["LAPTOP-001"],
    total: NumberDecimal("1299.99"),
    createdAt: new Date()
  }
]);

// === Usage $lookup Joined Queries(Similar SQL JOIN) ===
db.users.aggregate([
  { $match: { name: "Alice" } },
  { $lookup: {
      from: "orders",
      localField: "_id",
      foreignField: "user_id",
      as: "orders"
  }}
]);

الإخراج:

TEXT 📖 للعرض فقط
{
  _id: ObjectId('507f1f77bcf86cd799439011'),
  name: 'Alice',
  email: 'alice@example.com',
  orders: [
    {
      _id: ObjectId('507f1f77bcf86cd799439012'),
      user_id: ObjectId('507f1f77bcf86cd799439011'),
      items: ['PHONE-001', 'CASE-002'],
      total: NumberDecimal('649.98')
    },
    {
      _id: ObjectId('507f1f77bcf86cd799439013'),
      user_id: ObjectId('507f1f77bcf86cd799439011'),
      items: ['LAPTOP-001'],
      total: NumberDecimal('1299.99')
    }
  ]
}


9. تمرين عملي شامل: تصميم وثائق المستخدم الخاصة بالتجارة الإلكترونية

(1) متطلبات السيناريو

تصميم وثائق المستخدم لمنصة للتجارة الإلكترونية. المتطلبات:

(2) تصميم المستندات

JAVASCRIPT
// === Comprehensive User Documentation ===
db.users.insertOne({
  _id: ObjectId("507f1f77bcf86cd799439011"),

  // === Basic Information ===
  email: "alice@example.com",
  username: "alice_chen",
  displayName: "Alice Chen",
  phone: "+81-90-1234-5678",

  // === Certification ===
  passwordHash: "$2b$10$...",      // bcrypt Hash(Not explicitly stated)
  emailVerified: true,
  twoFactorEnabled: false,

  // === Preferences(Nested Documents)===
  preferences: {
    language: "ja",
    currency: "JPY",
    timezone: "Asia/Tokyo",
    notifications: {
      email: true,
      sms: false,
      push: true,
      marketing: false
    }
  },

  // === Shipping Address(Nested Arrays)===
  addresses: [
    {
      addressId: ObjectId("..."),
      type: "home",
      isDefault: true,
      street: "1-2-3 Shibuya",
      city: "Tokyo",
      prefecture: "Tokyo",
      zip: "150-0002",
      country: "Japan",
      phone: "+81-90-1234-5678"
    }
  ],

  // === Statistics(It is recommended to split fields that are updated frequently)===
  stats: {
    totalOrders: 25,
    totalSpent: NumberDecimal("125430.50"),
    averageRating: 4.7,
    lastOrderAt: ISODate("2026-06-15T10:30:00Z")
  },

  // === Watchlist(Quotation Style)===
  followingIds: [
    ObjectId("507f1f77bcf86cd799439012"),
    ObjectId("507f1f77bcf86cd799439013")
  ],

  // === Profile Picture Citation(GridFS)===
  avatarFileId: ObjectId("507f1f77bcf86cd799439099"),

  // === Metadata ===
  createdAt: ISODate("2025-03-01T10:00:00Z"),
  updatedAt: ISODate("2026-07-01T15:23:00Z"),
  lastLoginAt: ISODate("2026-07-01T10:00:00Z"),
  isActive: true,
  role: "customer"  // customer | admin | moderator
});

(3) تعيين مخطط مونجوس

JAVASCRIPT
const UserSchema = new mongoose.Schema({
  email: { type: String, required: true, unique: true, lowercase: true },
  username: { type: String, required: true, unique: true, index: true },
  displayName: { type: String, required: true },
  phone: { type: String },

  passwordHash: { type: String, required: true, select: false },
  emailVerified: { type: Boolean, default: false },
  twoFactorEnabled: { type: Boolean, default: false },

  preferences: {
    language: { type: String, default: 'en' },
    currency: { type: String, default: 'USD' },
    timezone: { type: String, default: 'UTC' },
    notifications: {
      email: { type: Boolean, default: true },
      sms: { type: Boolean, default: false },
      push: { type: Boolean, default: true },
      marketing: { type: Boolean, default: false }
    }
  },

  addresses: [{
    addressId: { type: mongoose.Schema.Types.ObjectId, default: () => new mongoose.Types.ObjectId() },
    type: { type: String, enum: ['home', 'work', 'other'], default: 'home' },
    isDefault: { type: Boolean, default: false },
    street: { type: String, required: true },
    city: { type: String, required: true },
    prefecture: String,
    zip: { type: String, required: true },
    country: { type: String, required: true },
    phone: String
  }],

  stats: {
    totalOrders: { type: Number, default: 0 },
    totalSpent: { type: mongoose.Schema.Types.Decimal128, default: 0 },
    averageRating: { type: Number, default: 0 },
    lastOrderAt: Date
  },

  followingIds: [{ type: mongoose.Schema.Types.ObjectId, ref: 'User' }],
  avatarFileId: { type: mongoose.Schema.Types.ObjectId },

  role: { type: String, enum: ['customer', 'admin', 'moderator'], default: 'customer', index: true },
  isActive: { type: Boolean, default: true, index: true }
}, { timestamps: true });


❓ أسئلة شائعة

س لماذا تستخدم MongoDB لغة BSON بدلاً من تخزين JSON مباشرةً؟
ج JSON هو تنسيق نصي بطيء في التحليل، ويحتوي على أنواع بيانات محدودة، ولا يضمن ترتيب الحقول. أما BSON فهو تنسيق ثنائي سريع التحليل (في أجزاء من الألف من الثانية)، ويدعم مجموعة غنية من أنواع البيانات (مثل Date و Binary و Decimal128)، ويحافظ على ترتيب الحقول (وهذا أمر مهم!)، مما يجعله أكثر ملاءمة لتخزين قواعد البيانات والاستعلام عنها.
س هل معرّف الكائن (ObjectId) فريد حقًّا؟
ج من الناحية النظرية، لن يتكرر نفس العداد داخل العملية نفسها خلال نفس الثانية. من الناحية العملية، فإن حدوث التداخلات مستحيل عمليًا (قيمة عشوائية مكونة من 5 بايت = 2^40 ≈ 1 تريليون احتمال). إذا كانت التفرّد المطلق مطلوبًا (على سبيل المثال، في المجال المالي)، فيمكنك تحديد _id: ObjectId() أو UUID يدويًّا.
س هل أسماء الحقول حساسة لحالة الأحرف؟
ج نعم. firstName وFirstName هما حقلان مختلفان. MongoDB حساسة تمامًا لحالة الأحرف. نوصي باستخدام قاعدة تسمية متسقة في جميع الأحوال (يُنصح باستخدام أسلوب camelCase).
س هل يمكن تعديل الحقل _id؟
ج نعم، لكن لا يُنصح بذلك. يُعد _id المعرّف الفريد للوثيقة، وتعديله سيؤدي إلى كسر علاقات الإحالة. إذا كنت بحاجة إلى مفتاح أساسي خاص بالأعمال (مثل رقم الطلب)، فيمكنك استخدام مفتاح أساسي مخصص مثل _id: "ORDER-2026-07-001"، لكن هذا سيؤدي إلى إبطاء أداء الاستعلامات.
س كيف أختار بين Decimal128 و Double؟
ج استخدم Decimal128 في المجال المالي والمبالغ النقدية والحسابات الدقيقة. واستخدم Double (الذي يعتبر أسرع) في الحوسبة العلمية والإحصاءات وعرض الرسومات. وقد يؤدي الاستخدام غير الصحيح لـ Decimal128 إلى مشكلة 0.1 + 0.2 = 0.30000000000000004 الشهيرة.
س هل أداء الاستعلام جيد بالنسبة للمستندات المضمنة؟
ج نعم. بمجرد قيام MongoDB بفهرسة المستندات المضمنة، يصبح أداء الاستعلام مماثلاً لأداء المجموعات المستقلة. ومع ذلك، يرجى ملاحظة ما يلي: (1) لا يمكن أن يتجاوز الحجم الإجمالي للمستند 16 ميغابايت؛ (2) توجد قيود على حجم الفهارس متعددة المفاتيح في حقول المصفوفات.
س هل يمكن أن تكون أسماء الحقول باللغة الصينية؟
ج نعم، لكن لا يُنصح بذلك. على سبيل المثال، { name: "Alice" } اسم صالح، لكنه لا يوفر الدعم الكافي لأغراض تصحيح الأخطاء والتسجيل وأدوات الجهات الخارجية. نوصي باستخدام اللغة الإنجليزية في جميع الأحوال.

📖 ملخص


📝 تمارين

  1. السؤال الأساسي (⭐): أدخل مستند منتج إلى Mongosh (يحتوي على 6 حقول أو أكثر: سلسلة، رقم، تاريخ، مصفوفة، كائن، قيمة منطقية)، ثم استخدم findOne() للاستعلام عن أنواع الحقول والتحقق منها.

  2. تمرين أساسي (⭐): اكتب برنامجًا نصيًّا بلغة Node.js لإنشاء مخطط بيانات للمستخدم باستخدام Mongoose (بما في ذلك حقل balance من النوع Decimal128 وحقل avatar من النوع Buffer)، وإدراج البيانات، وعرض الطابع الزمني لـ ObjectId.

  3. تمرين متقدم (⭐⭐): صمم بنية مستند لمقال مدونة (يحتوي على 5 حقول أو أكثر)، واستخدم insertMany لإدراج 5 مقالات، ووضح تصميم مصفوفة تعليقات مدمجة.

  4. مشكلة متقدمة (⭐⭐): اكتب برنامجًا نصيًّا لاستخراج الطوابع الزمنية من حقول ObjectId في 100 مستند، وتجميعها حسب التاريخ، وحساب عدد المستندات لكل يوم.

  5. مشكلة متقدمة (⭐⭐): قارن أداء الاستعلامات الخاصة بالوثائق المضمنة والمشار إليها: باستخدام مليون سجل، قم بتخزين العلاقة «المستخدم-الطلب» باستخدام كل من طريقتي التخزين المضمنة والمشار إليها، وقم بقياس أوقات الاستجابة باستخدام $lookup والاستعلامات المتداخلة.

  6. التحدي (⭐⭐⭐): استخدم GridFS لتنفيذ واجهة برمجة تطبيقات (API) لتحميل/تنزيل الملفات تدعم تحميل ملفات يصل حجمها إلى 100 ميغابايت، وتتحقق من آلية التخزين المقسمة إلى أجزاء، وتنفذ تتبع تقدم عملية التنزيل.

Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%