MongoDB: الوثائق وBSON: حجر الأساس لبيانات MongoDB
آخر تحديث: 2026-08-26
BSON هو تنسيق البيانات الخاص بـ MongoDB — وهو يوسع نطاق إمكانيات JSON ويدعم الأنواع الأصلية مثل Date و Binary و Decimal128.
تقدم هذه الدورة فهمًا متعمقًا لتنسيق البيانات BSON، والبنية الداخلية لـ ObjectId، ونظام أنواع الحقول، كما تعلّم أفضل الممارسات لتصميم المستندات.
1. ما ستتعلمه
- الفرق الجوهري بين تنسيق البيانات BSON وتنسيق JSON
- البنية الداخلية لوثائق MongoDB (_id، الحقول، القيم)
- تكوين ObjectId، واستخراج الطابع الزمني، وضمان التفرد
- 12 نوعًا من أنواع بيانات BSON (سلسلة، رقم، تاريخ، مصفوفة، كائن، معرّف الكائن، إلخ)
- قواعد تسمية الحقول (CamelCase مقابل SnakeCase مقابل Kebab-Case)
- فلسفة التصميم الكامنة وراء الحد الأقصى لحجم المستند (16 ميغابايت)
- استراتيجيات الاختيار بين التوثيق المضمن والإحالات المرجعية
2. قصة حقيقية لمهندس متكامل
(1) المشكلة: يتم تحويل التواريخ إلى سلاسل نصية عند تخزين JSON في MongoDB
تشارلي هو مهندس متكامل في Node.js يعمل حالياً على ترحيل بيانات MySQL إلى MongoDB:
"قمت بتحويل بيانات الطلبات من MySQL إلى JSON وتخزينها في MongoDB، لأكتشف بعد ذلك أن جميع التواريخ قد تم تحويلها إلى السلسلة
new Date()، والتي تعذر تحليلها؛ كما فقدت دقة المبالغ (0.1 + 0.2 ≠ 0.3)؛ ولم يكن بالإمكان تخزين صور الملف الشخصي الثنائية على الإطلاق."
قام بتسلسل بيانات الطلبات باستخدام JSON.stringify()، مما أدى إلى فقدان معلومات النوع:
// ❌ 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 الثنائي)، مع الحفاظ على جميع معلومات الأنواع.
// ✅ 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% في الحجم الإضافي المطلوب (لتخزين معلومات النوع والطول).
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. وتشمل ميزاته ما يلي:
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
تحليل النقاط الرئيسية:
- ترتيب إدراج الحقول في BSON — وهذا أمر بالغ الأهمية لفهرسة البيانات وتحسين الاستعلامات في MongoDB
- يسبق كل حقل رمز نوع مكون من بايت واحد، مما يتيح لـ BSON التمييز بين «التاريخ» و«السلسلة» (على عكس JSON).
- يتم تخزين المستندات والمصفوفات المتداخلة بشكل متكرر في BSON، بحد أقصى لعمق التداخل يبلغ 100 مستوى.
- يظهر الحقل
_idدائمًا في بداية المستند، مما يساهم في تحسين أداء الاستعلام
// 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
// 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' } }
// 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 فريد خلال ثانية واحدة.
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 بت):
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
تحليل النقاط الرئيسية:
- يقوم جزء الطابع الزمني في ObjectId تلقائيًّا بفرز الإدخالات حسب وقت الإدراج — مما يتيح إجراء الاستعلامات حسب النطاق الزمني دون الحاجة إلى فهارس
createdAtإضافية. - يتم إنشاء قيمة عشوائية مكونة من 5 بايت وتخزينها مؤقتًا عند بدء العملية، مما يضمن تفردها عبر العمليات المختلفة (2^40 ≈ 1 تريليون احتمال).
- يزداد عداد الـ 3 بايت بمقدار واحد في غضون ثانية واحدة، مما ينتج عنه 2^24 ≈ 16.77 مليون معرّف فريد في الثانية.
- يمكن لطريقة
getTimestamp()استخراج وقت الإنشاء مباشرةً من ObjectId دون الحاجة إلى استعلامات إضافية.
// 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")
// 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)
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
// === 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 بايت | المبلغ المالي (موصى به) |
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: التعامل مع الأنواع العددية
// === 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
✅ التسمية الصحيحة:
- لا يجوز أن تبدأ أسماء الحقول بـ
$(كلمة محجوزة) - لا يجوز أن تحتوي أسماء الحقول على
.(مع الاحتفاظ بترقيم النقاط) - لا يجوز أن تكون أسماء الحقول عبارة عن سلاسل فارغة
""
// ✅ 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
// ✅ 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
// 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: تجنب تخزين المستندات الضخمة:
- ✅ تُرجع المستند بأكمله في استعلام واحد (بدون JOIN)
- ✅ كفاءة عالية في نقل المستندات (مناسبة للنقل عبر الشبكة)
- ❌ غير مناسب لتخزين الملفات الثنائية الكبيرة (استخدم GridFS)
- ❌ غير مناسب لتخزين النصوص الطويلة جدًّا (استخدم Elasticsearch)
(3) حلول لسيناريوهات المستندات الكبيرة
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
// === 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 ودورة حياة خاصة بها؛ ولا تؤثر التحديثات التي تُجرى على أحدهما على الآخر، لكن الاستعلام عنها يتطلب عملية ربط إضافية.
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) استراتيجيتان لنمذجة العلاقات
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) مثال على مستند مضمن
// === 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: مثال على وثيقة مكتوبة بأسلوب الاقتباس
// === 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) متطلبات السيناريو
تصميم وثائق المستخدم لمنصة للتجارة الإلكترونية. المتطلبات:
- المعلومات الأساسية للمستخدم (الاسم، عنوان البريد الإلكتروني، تاريخ التسجيل)
- عناوين شحن متعددة (مضمنة)
- الإعدادات (اللغة، العملة، الإشعارات)
- المعلومات الإحصائية (إجمالي عدد الطلبات، إجمالي المبيعات)
- قائمة المتابعين (تتضمن أسماء مستخدمين آخرين)
- صورة الملف الشخصي (مرجع GridFS)
(2) تصميم المستندات
// === 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) تعيين مخطط مونجوس
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 });
❓ أسئلة شائعة
_id: ObjectId() أو UUID يدويًّا.firstName وFirstName هما حقلان مختلفان. MongoDB حساسة تمامًا لحالة الأحرف. نوصي باستخدام قاعدة تسمية متسقة في جميع الأحوال (يُنصح باستخدام أسلوب camelCase)._id؟_id المعرّف الفريد للوثيقة، وتعديله سيؤدي إلى كسر علاقات الإحالة. إذا كنت بحاجة إلى مفتاح أساسي خاص بالأعمال (مثل رقم الطلب)، فيمكنك استخدام مفتاح أساسي مخصص مثل _id: "ORDER-2026-07-001"، لكن هذا سيؤدي إلى إبطاء أداء الاستعلامات.0.1 + 0.2 = 0.30000000000000004 الشهيرة.{ name: "Alice" } اسم صالح، لكنه لا يوفر الدعم الكافي لأغراض تصحيح الأخطاء والتسجيل وأدوات الجهات الخارجية. نوصي باستخدام اللغة الإنجليزية في جميع الأحوال.📖 ملخص
- BSON هو تنسيق التخزين الثنائي الخاص بـ MongoDB، والذي يدعم أنواع بيانات أكثر من JSON (مثل Date و Decimal128 و Binary).
- ObjectId هو معرّف فريد مكون من 12 بايت، يتألف من طابع زمني وقيمة عشوائية وعداد.
- يدعم BSON أكثر من 12 نوعًا من أنواع البيانات، مع التركيز على التمييز بين أنواع Double وInt32 وLong وDecimal128
- نوصي باستخدام أسلوب «camelCase» في تسمية الحقول؛ وتجنب الأسماء التي تبدأ بـ
$أو التي لا تحتوي على.. - الحد الأقصى لحجم المستند الواحد هو 16 ميغابايت؛ استخدم GridFS للملفات الكبيرة وElasticsearch للنصوص الطويلة.
- تُعد العلاقات المضمنة مناسبة للعلاقات من النوع 1:1 ولعدد قليل من العلاقات من النوع 1:N، في حين أن العلاقات المرجعية مناسبة للعلاقات المعقدة
- يحدد مخطط Mongoose هياكل المستندات ويتولى تحويلات أنواع BSON تلقائيًا
📝 تمارين
-
السؤال الأساسي (⭐): أدخل مستند منتج إلى Mongosh (يحتوي على 6 حقول أو أكثر: سلسلة، رقم، تاريخ، مصفوفة، كائن، قيمة منطقية)، ثم استخدم
findOne()للاستعلام عن أنواع الحقول والتحقق منها. -
تمرين أساسي (⭐): اكتب برنامجًا نصيًّا بلغة Node.js لإنشاء مخطط بيانات للمستخدم باستخدام Mongoose (بما في ذلك حقل
balanceمن النوعDecimal128وحقلavatarمن النوعBuffer)، وإدراج البيانات، وعرض الطابع الزمني لـObjectId. -
تمرين متقدم (⭐⭐): صمم بنية مستند لمقال مدونة (يحتوي على 5 حقول أو أكثر)، واستخدم
insertManyلإدراج 5 مقالات، ووضح تصميم مصفوفة تعليقات مدمجة. -
مشكلة متقدمة (⭐⭐): اكتب برنامجًا نصيًّا لاستخراج الطوابع الزمنية من حقول ObjectId في 100 مستند، وتجميعها حسب التاريخ، وحساب عدد المستندات لكل يوم.
-
مشكلة متقدمة (⭐⭐): قارن أداء الاستعلامات الخاصة بالوثائق المضمنة والمشار إليها: باستخدام مليون سجل، قم بتخزين العلاقة «المستخدم-الطلب» باستخدام كل من طريقتي التخزين المضمنة والمشار إليها، وقم بقياس أوقات الاستجابة باستخدام
$lookupوالاستعلامات المتداخلة. -
التحدي (⭐⭐⭐): استخدم GridFS لتنفيذ واجهة برمجة تطبيقات (API) لتحميل/تنزيل الملفات تدعم تحميل ملفات يصل حجمها إلى 100 ميغابايت، وتتحقق من آلية التخزين المقسمة إلى أجزاء، وتنفذ تتبع تقدم عملية التنزيل.