قواعد البيانات والتخزين

قواعد البيانات العلائقية وضمانات ACID

18 دقيقة الدرس 2 من 10

قواعد البيانات العلائقية وضمانات ACID

تُشكّل قواعد البيانات العلائقية العمود الفقري لأنظمة الإنتاج منذ سبعينيات القرن الماضي. فـPostgreSQL يشغّل GitHub وInstagram وShopify، وMySQL يدير WordPress وعدداً لا يُحصى من منتجات SaaS. إن فهم لماذا صُمِّمت على هذا النحو — الجداول والمفاتيح والعلاقات وعقد ACID — أمرٌ لا غنى عنه قبل أن تتمكن من اتخاذ قرارات مستنيرة حول متى تستخدمها ومتى تلجأ إلى بديل آخر.

النموذج العلائقي باختصار

الجدول (Relation) هو مجموعة من السجلات (الصفوف) التي تشترك جميعها في نفس المخطط (الأعمدة). لكل عمود نوع بيانات محدد وقيود اختيارية. كل صف في الجدول المُصمَّم تصميماً سليماً يُعرَّف بشكل فريد بواسطة المفتاح الأساسي (Primary Key) — وهو مجموعة أدنى من الأعمدة التي لا تتكرر قيمها بين الصفوف.

تترابط الجداول ببعضها من خلال المفاتيح الأجنبية (Foreign Keys): عمود في جدول يُشير إلى المفتاح الأساسي لجدول آخر. يُطبّق ذلك تكامل المراجع — لا يمكنك إنشاء طلب لعميل غير موجود، ولا يمكنك حذف عميل لديه طلبات (إلا إذا استخدمت CASCADE أو SET NULL).

Relational table relationships: customers, orders, order_items customers 🔑 id (PK, int) name (varchar) email (varchar, unique) country (varchar) created_at (timestamp) orders 🔑 id (PK, int) 🔗 customer_id (FK) total_cents (int) status (enum) placed_at (timestamp) order_items 🔑 id (PK, int) 🔗 order_id (FK) product_id (int) quantity (int) unit_price_cents (int) FK FK 1 customer many orders many items per order
ثلاثة جداول مترابطة — لكل عميل طلبات متعددة، ولكل طلب عناصر متعددة. تُطبّق المفاتيح الأجنبية تكامل المراجع.

التطبيع: لماذا نتجنب التكرار

التطبيع (Normalization) هو عملية هيكلة الجداول لتقليل ازدواجية البيانات ومنع تناقضات التحديث. المثال الكلاسيكي: إذا خزّنت بريد العميل الإلكتروني في كل صف من جدول orders، فعند تغيير البريد ستحتاج إلى تحديث آلاف الصفوف — وإذا فاتك صف واحد أصبحت بياناتك متناقضة. الاحتفاظ به في مكان واحد (جدول customers) يعني تحديثاً واحداً صحيحاً دائماً.

القاعدة العملية: كل عمود غير مفتاحي يجب أن يعتمد على المفتاح كاملاً لا جزءاً منه. عملياً، ضع كل كيان في جدوله الخاص وأشر إليه بالمعرّف.

التطبيع مقابل الأداء: تتطلب المخططات المُطبَّعة بشكل مكثف عمليات JOIN لإعادة تجميع البيانات. عمليات JOIN على جداول كبيرة مع فهارس مناسبة تكون سريعة، لكنها تضيف تعقيداً. كثير من الأنظمة عالية الحجم تُلغي التطبيع الانتقائي لمسارات الاستعلام الساخنة — وهو مقايضة نتناولها في الدرس السابع.

المعاملات: وحدة العمل

المعاملة (Transaction) هي سلسلة من عبارات SQL تتعامل معها قاعدة البيانات كعملية منطقية واحدة. المثال الكلاسيكي هو التحويل البنكي: خصم من حساب A، وإضافة إلى حساب B. يجب أن ينجح الأمران معاً، أو لا يُثبَّت أيٌّ منهما. لا تريد أبداً حالة خُصم فيها A دون أن يُضاف لـB.

BEGIN; UPDATE accounts SET balance = balance - 500 WHERE id = 42; UPDATE accounts SET balance = balance + 500 WHERE id = 99; COMMIT; -- في حال فشل أي شيء قبل COMMIT: ROLLBACK; -- يتراجع عن كلا التحديثين

تضمن قاعدة البيانات أن المعاملة إما تُنفَّذ بالكامل، أو يُتراجع عنها تاركةً كلا الصفين دون تغيير. هذا هو أساس عقد ACID.

ACID — الضمانات الأربع

ACID اختصار لأربع خصائص يجب أن تحققها كل معاملة علائقية. كل خاصية تحل نمطاً محدداً من أنماط الفشل.

ACID guarantees: Atomicity, Consistency, Isolation, Durability Atomicity الكل أو لا شيء. كل عبارة في المعاملة تُنفَّذ أو لا شيء. تعطل أثناء التحويل؟ الصفان يعودان لحالتهما. الآلية: Write-Ahead Log (WAL) + rollback Consistency القواعد دائماً محترمة. كل معاملة مُثبَّتة تترك القاعدة في حالة صحيحة. FK, CHECK, UNIQUE مُطبَّقة تلقائياً. الآلية: فحص القيود عند الإيداع Isolation المعاملات المتزامنة لا تتداخل. العمل الجاري غير مرئي حتى الإيداع. Read Committed هو الإعداد الشائع. الآلية: MVCC / القفل Durability المُثبَّت = آمن. البيانات تنجو من الأعطال وانقطاع الكهرباء والإعادة. COMMIT يعود فقط بعد fsync على القرص. الآلية: WAL مدفوع إلى تخزين دائم كل حرف في ACID يحل نمط فشل مختلف
الضمانات الأربع لـACID، معناها العملي، والآلية التي تستخدمها قاعدة البيانات لتطبيق كل منها.

نظرة أعمق على كل خاصية

الذرية (Atomicity) — تستخدم قاعدة البيانات سجل الكتابة المسبقة (WAL). قبل تغيير أي صفحة بيانات على القرص، تُكتب التغييرات المقصودة في WAL. إذا انهار الخادم في منتصف المعاملة، تُعيد عملية الاسترداد تنفيذ المعاملات المكتملة وتتراجع عن غير المكتملة. من منظور التطبيق، إما أن المعاملة حدثت بالكامل أو لم تحدث أصلاً.

الاتساق (Consistency) — هذا جزئياً مسؤولية قاعدة البيانات (تطبيق القيود) وجزئياً مسؤولية التطبيق (عدم انتهاك قواعد العمل). يفحص PostgreSQL قيود NOT NULL وUNIQUE وCHECK وFOREIGN KEY عند الإيداع. إذا فشل أي فحص، تُلغى المعاملة بالكامل تلقائياً.

العزل (Isolation) — هو أكثر الخصائص دقة. يحدد معيار SQL أربعة مستويات عزل تُوازن بين الصحة والتزامن:

  • Read Uncommitted — يمكن رؤية الكتابات غير المُثبَّتة من المعاملات الأخرى. نادراً ما يُستخدم.
  • Read Committed — يرى فقط البيانات المُثبَّتة. الافتراضي في PostgreSQL وأغلب الأنظمة. يمنع القراءات غير النظيفة، لكن يسمح بـالقراءات غير القابلة للتكرار.
  • Repeatable Read — يُؤخذ لقطة من البيانات عند بدء المعاملة؛ إعادة القراءات مستقرة. الافتراضي في MySQL/InnoDB.
  • Serializable — تُنفَّذ المعاملات كما لو كانت متسلسلة. أعلى صحة، وأعلى تكلفة. استخدمها للدفاتر المالية وخصم المخزون.
الإعداد العملي: ابدأ بـRead Committed لمعظم أحمال عمل واجهة API. ارتقِ إلى Repeatable Read أو Serializable فقط للمعاملات التي تجمّع البيانات ثم تكتب قرارات بناءً عليها — مثل "تحقق من عدد المقاعد، ثم احجز مقعداً".

تُطبّق معظم قواعد البيانات الحديثة العزل من خلال MVCC (التحكم في التزامن متعدد الإصدارات): كل كاتب ينشئ إصداراً جديداً من الصف بدلاً من قفله في مكانه. يرى القرّاء الإصدار الحالي وقت لقطتهم. يتيح ذلك تزامناً عالياً في القراءة دون حجب الكتابة.

الديمومة (Durability) — لا يعود COMMIT بنجاح حتى يُدفَع سجل WAL إلى تخزين دائم (fsync على القرص أو SSD). لهذا قد تستغرق الإيداعات على الأقراص الدوارة ميلي ثانية. قواعد البيانات السحابية (Aurora, Cloud SQL) تُكرّر سجلات WAL عبر مناطق توفر متعددة — يعود الإيداع فقط بعد أن تُقرّ نسبة أغلبية من العقد، مما يمنح ديمومة تصمد أمام فشل مركز بيانات كامل.

ثمن ACID: المقايضات مع الأداء

ضمانات ACID ليست مجانية. لكل خاصية ثمنها:

  • الذرية والديمومة — كل إيداع يتطلب fsync. على عقدة واحدة، يتحمل PostgreSQL نحو 1,000 إلى 5,000 معاملة كتابة في الثانية قبل أن يصبح WAL عائق الأداء. استخدام الإيداع الجماعي (تجميع عدة معاملات في fsync واحد) يرفع ذلك إلى عشرات الآلاف.
  • العزل — تتطلب مستويات العزل الأعلى مزيداً من القفل أو إصدارات MVCC أكثر، مما يزيد ضغط الذاكرة واحتمال الجمود. يمكن أن يُخفّض العزل القابل للتسلسل الإنتاجية بنسبة 30–50% مقارنةً بـRead Committed على قاعدة بيانات OLTP نشطة.
  • الاتساق — تتطلب قيود المفاتيح الأجنبية بحث فهرس عند كل INSERT أو DELETE. إزالة المفاتيح الأجنبية تزيل هذا الحمل — لكنها تزيل الشبكة الآمنة أيضاً.
لا تُعطّل الديمومة من أجل الإنتاجية — تتيح بعض الأدوات ضبط synchronous_commit = off في PostgreSQL، مما يعني عودة COMMIT قبل دفع WAL. يُحسّن ذلك زمن استجابة الكتابة، لكنه يُدخل نافذة خطر تصل إلى ~200 مللي ثانية من فقدان البيانات المُيدَعة عند التعطل. هذه مقايضة صحيحة فقط لبيانات يمكن إعادة توليدها (أحداث تحليلية، استيعاب سجلات) — وليس أبداً للبيانات المالية أو سجلات المستخدمين.

اختيار قاعدة بيانات علائقية للتوسع

عند الحجم الصغير (ملايين الصفوف، بضع مئات من الاستعلامات في الثانية) أي قاعدة بيانات علائقية مُفهرَسة بشكل صحيح تكون سريعة. عند التوسع:

  • توسعة القراءة — أضف نسخاً للقراءة (الدرس 5). تتوزع القراءات؛ الكتابات لا تزال تذهب إلى مصدر واحد.
  • توسعة الكتابة — التوسع الرأسي (جهاز أكبر) يمنح وقتاً. بعد ذلك، التقسيم والتشارد (الدرس 6) أو نقل بعض أعباء العمل إلى مخازن متخصصة.
  • تجميع الاتصالات — يُولّد PostgreSQL عملية لكل اتصال؛ عند 10,000 اتصال يصل استخدام الذاكرة وحده إلى ~10 جيجابايت. PgBouncer (تجميع وضع المعاملة) يتيح لآلاف خيوط التطبيق مشاركة عشرات اتصالات قاعدة البيانات الفعلية.
قاعدة إبهام: تُعالج عقدة PostgreSQL واحدة مُضبَّطة جيداً على معدات حديثة (32 نواة، NVMe SSD) مئات الآلاف من القراءات في الثانية وعشرات الآلاف من الكتابات التعاملية في الثانية. معظم التطبيقات لا تتجاوز أبداً مصدراً واحداً. أضف نسخاً قبل أن تفكر في NoSQL.

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