تخطَّ إلى المحتوى

🎸 شرح Django

خيارات الحذف على مستوى قاعدة البيانات

الدرس 35 من 36· ⏱ 3 دقائق قراءة

تذكير: on_delete التقليدية تعمل من جهة Django

تعلّمت بدرس العلاقات أن on_delete=models.CASCADE تحذف السجلات المرتبطة تلقائيًا. لكن آلية العمل التقليدية تمرّ دائمًا عبر Django نفسه: عند حذف سجل، Django يجلب كل السجلات المرتبطة أولًا (استعلام SELECT)، ثم يحذفها واحدة واحدة أو بجملة DELETE منفصلة — حتى لو كانت قاعدة البيانات نفسها تدعم CASCADE على مستوى القيد (constraint) مباشرة.

الجديد في Django 6.1: خيارات على مستوى قاعدة البيانات

بدءًا من Django 6.1، صار on_delete يقبل مجموعة قيم إضافية تُنفَّذ داخل قاعدة البيانات نفسها عبر قيد ON DELETE بجدول SQL، بدل المرور بـ Django:

from django.db import models

class Post(models.Model):
    author = models.ForeignKey(
        "Author",
        on_delete=models.DB_CASCADE,   # ← تُنفَّذ داخل قاعدة البيانات مباشرة
    )

القيم الجديدة تُستورَد من django.db.models مثل القيم التقليدية تمامًا: DB_CASCADE، DB_PROTECT، DB_RESTRICT، DB_SET_NULL، DB_SET_DEFAULT، وDB_DO_NOTHING — كل واحدة تقابل نظيرتها التقليدية بالسلوك المنطقي، لكن بتنفيذ مختلف جذريًا.

لماذا أسرع؟

مع CASCADE التقليدية، حذف كاتب له 10,000 منشور يعني: جلب كل المنشورات لذاكرة Django أولًا، ثم حذفها. مع DB_CASCADE، قاعدة البيانات تحذفها مباشرة بجملة SQL واحدة دون أن يلمسها Django إطلاقًا — أسرع بكثير مع كميات بيانات كبيرة.

قيدان مهمّان قبل الاستخدام

1. لا إشارات pre_delete / post_delete للسجلات المرتبطة

# ❌ لن يُستدعى إطلاقًا للمنشورات المحذوفة عبر DB_CASCADE
@receiver(pre_delete, sender=Post)
def log_post_deletion(sender, instance, **kwargs):
    print(f"سيُحذف: {instance.title}")

بما أن قاعدة البيانات هي من تحذف السجلات المرتبطة مباشرة، Django لا "يعلم" بحذفها فرديًا، فلا تُطلَق إشاراته إطلاقًا لتلك السجلات — فقط للسجل الأساسي الذي استدعيت عليه .delete().

2. نتيجة delete() تصبح جزئية

author.delete()
# مع CASCADE التقليدية: (10001, {'blog.Author': 1, 'blog.Post': 10000})
# مع DB_CASCADE: (1, {'blog.Author': 1})  ← لا تُحصي المنشورات المحذوفة!

القاموس المُرجَع من delete() يُبلّغ فقط عن الكائنات التي تعامل معها Django نفسه. السجلات المحذوفة عبر قيد قاعدة البيانات لا تظهر بالعدّ، لأن Django لا يعرف عددها أصلًا.

جدول سريع: التقليدية مقابل مستوى قاعدة البيانات

المعيارCASCADE (تقليدية)DB_CASCADE (جديدة)
مكان التنفيذكود Django (Python)قاعدة البيانات مباشرة (SQL)
الأداء مع بيانات كبيرةأبطأ — يجلب السجلات أولًاأسرع بكثير — بلا جلب مسبق
إشارات pre_delete/post_deleteتُطلَق لكل سجل مرتبطلا تُطلَق للسجلات المحذوفة عبر القيد
دقّة نتيجة delete()كاملة ومفصّلةجزئية — لا تُحصي المحذوف بالقيد

💡 استخدم الخيارات الجديدة (DB_CASCADE وأخواتها) لعلاقات بحجم بيانات كبير لا تعتمد منطقك على إشارات الحذف. أبقِ الخيارات التقليدية (CASCADE, PROTECT, ...) حيث تحتاج التحكم الكامل أو منطق إشارات مخصّص.

أخطاء شائعة

  • استبدال كل CASCADE بـ DB_CASCADE تلقائيًا بمشروع قائم يعتمد على إشارة post_delete لتنظيف ملفات مرتبطة أو تسجيل نشاط — يتوقّف هذا المنطق عن العمل بصمت للسجلات المحذوفة بالقيد.
  • الاعتماد على القاموس المُرجَع من delete() لعدّ كل ما تأثّر بالعملية عند استخدام خيار DB-level — العدّ يكون جزئيًا كما شرحنا أعلاه.
  • توقّع أن DB_RESTRICT/DB_PROTECT يمنعان الحذف بنفس رسالة الخطأ التقليدية بالضبط — الاستثناء يأتي غالبًا من قاعدة البيانات نفسها (IntegrityError) لا من طبقة Django كما بالخيار التقليدي.

🎯 التالي: الخلاصة الشاملة لمسار Django وخطواتك بعده.

شرح خيارات الحذف على مستوى قاعدة البيانات — Django بالعربي
خيارات الحذف على مستوى قاعدة البياناتDjango بالعربي · The Code Fix

📚 لمزيد من التعمّق في Django، راجِع التوثيق الرسمي لـ Django.

هل كان هذا الدرس مفيدًا؟