Menu
flag Ar iconالعربيةdown icon

إعدادات PRAGMA في SQLite لبيئة الإنتاج

تعرّف على أهم أوامر PRAGMA في SQLite التي تستحق الضبط فعلاً: journal_mode وsynchronous وforeign_keys وbusy_timeout وcache_size، مع القيم الموصى بها للإنتاج.

تحتوي هذه الصفحة على محررات قابلة للتشغيل — حرّر، شغّل، وشاهد النتيجة فوراً.

PRAGMA: لغتك للتفاهم مع محرك SQLite

تُعدّ تعليمات PRAGMA أوامر خاصة بـ SQLite تتيح لك قراءة إعدادات المحرّك أو تعديل سلوكه. تُنفَّذ تماماً مثل أي جملة SQL أخرى، لكنها لا تمسّ بياناتك، بل تتعامل مع إعدادات قاعدة البيانات نفسها.

عند تنفيذ PRAGMA كاستعلام، فإنه يُرجع القيمة الحالية. أما عند تنفيذه كعملية تعيين، فإنه يُغيّر القيمة:

الفكرة الأساسية التي يجب أن تثبت في ذهنك: معظم إعدادات PRAGMA في SQLite تُطبَّق على مستوى الاتصال الواحد فقط. بمجرد ما تفتح اتصالاً جديداً، ترجع كل القيم الافتراضية كما كانت. ولهذا السبب، أي كود جاهز للإنتاج تلاقيه يحتوي عادةً على مجموعة صغيرة من تعليمات PRAGMA يتم تنفيذها مباشرةً بعد فتح كل اتصال.

الإعدادات الأساسية لضبط SQLite للإنتاج

لو ما تقدر تحفظ غير خمس PRAGMAs، فاحفظ هذي الخمسة:

هذا الإعداد افتراضي منطقي لأي تطبيق تقريبًا يستخدم SQLite كمخزن رئيسي للبيانات. كل خيار من هذه الخيارات يستحق أن تفهمه على حدة، وبقية هذه الصفحة تشرح لك كل واحد منها بالتفصيل.

وضع journal_mode = WAL

يتحكم وضع journaling في الطريقة التي يضمن بها SQLite ديمومة عمليات الكتابة. الوضع الافتراضي DELETE يعتمد على rollback journal، أي أن عمليات الكتابة تُعطّل القراءة، والقراءة تُعطّل الكتابة. قد يكون هذا مقبولًا في أداة سطر أوامر بسيطة، لكنه كارثي في تطبيق ويب.

أما WAL (اختصارًا لـ Write-Ahead Logging) فيقلب المعادلة رأسًا على عقب. القرّاء والكُتّاب لا يُعطّل بعضهم بعضًا، ويرى القارئ لقطة (snapshot) ثابتة من البيانات بينما الكاتب في منتصف عملية commit. ستظل الكتابة محصورة بكاتب واحد في كل مرة، لكن القراءة تبقى سريعة حتى تحت الحِمل العالي.

بعض الأمور المهمة التي ينبغي معرفتها:

  • خاصية journal_mode دائمة — بمجرد ضبطها، تبقى كذلك لملف قاعدة البيانات. لست مضطرًا لضبطها مع كل اتصال، لكن لا ضرر في ذلك.
  • يُنشئ وضع WAL ملفّين إضافيّين بجوار ملف .db: ملف -wal وآخر -shm. لا تحذفهما ما دامت قاعدة البيانات مفتوحة.
  • وضع WAL لا يعمل بكفاءة مع أنظمة الملفات الشبكية (NFS، SMB). احتفظ بقاعدة البيانات على قرص محلي.

هناك مستند منفصل يتناول وضع WAL والتعامل المتزامن بتعمق أكبر. أمّا الآن: فقط فعّله.

ضبط synchronous على NORMAL

تتحكم خاصية synchronous في مدى صرامة SQLite عند إفراغ البيانات إلى القرص. والمعادلة هنا بين الموثوقية والسرعة.

  • FULL (الافتراضي) — يُفرغ البيانات بعد كل عملية commit. أقصى درجات الموثوقية، لكن أبطأ.
  • NORMAL — يُفرغ البيانات عند نقاط تفتيش آمنة. آمن مع وضع WAL، وأسرع.
  • OFF — يترك القرار لنظام التشغيل. سريع، لكنه يُعرّضك لخطر تلف البيانات عند انقطاع التيار.

الرقم الصحيح في النتيجة (1) يقابل القيمة NORMAL. مع وضع WAL، تُعدّ NORMAL هي الإعداد المُوصى به — فأنت لن تفقد المعاملات المُثبَّتة (committed) عند تعطّل التطبيق، وكل ما يمكن أن يحدث هو فقدان آخر معاملة أو اثنتين في حال انقطاع التيار الكهربائي. وهذا التوازن مناسب لمعظم التطبيقات.

تجنّب استخدام OFF إلا إذا كنت تملأ قاعدة بيانات مؤقتة يمكنك إعادة إنشائها من الصفر.

تفعيل المفاتيح الأجنبية: foreign_keys = ON

هذه النقطة تُربك الكثيرين. صحيح أن SQLite يدعم المفاتيح الأجنبية (foreign keys)، لكن التحقق منها مُعطَّل افتراضيًا، كما أن هذا الإعداد يُضبط لكل اتصال على حدة:

عند تفعيل foreign_keys = ON، ستفشل عملية الإدراج الأخيرة لأنه ببساطة لا يوجد مؤلف بالـ id رقم 999. أما بدون هذا الـ PRAGMA، فإن SQLite سيكتب الصف اليتيم بكل أريحية، ولن تكتشف الفوضى إلا بعد أشهر.

شغّل PRAGMA foreign_keys = ON; كأول تعليمة في كل اتصال جديد. معظم مكتبات الـ ORM تفعل ذلك تلقائيًا، لكن إن كنت تستخدم الـ driver الخام مباشرة، فالمسؤولية تقع على عاتقك.

busy_timeout = 5000

يسمح SQLite بكاتب واحد فقط في كل لحظة. فإذا حاول اتصال ثانٍ الكتابة بينما الأول في منتصف معاملة، فسيحصل على SQLITE_BUSY وينسحب فورًا، هذا هو السلوك الافتراضي.

دور PRAGMA busy_timeout هو إخبار SQLite بأن ينتظر ويعيد المحاولة بدلًا من ذلك:

القيمة بالمللي ثانية. الرقم 5000 يعني: "انتظر القفل لمدة تصل إلى 5 ثوانٍ قبل الاستسلام". وعند دمج هذا الإعداد مع وضع WAL، فإنه يقضي على معظم أخطاء database is locked العشوائية في التطبيقات التي تتعامل مع التزامن.

إذا وجدت نفسك ترفع هذه القيمة فوق 30 ثانية، فالحل الحقيقي على الأرجح هو تقصير المعاملات، لا إطالة المهلة.

cache_size

يحدد cache_size عدد صفحات قاعدة البيانات التي يحتفظ بها SQLite في الذاكرة. وكلما زاد حجم الكاش، قلّت عمليات القراءة من القرص، وبالتالي تسرع الاستعلامات على البيانات النشطة.

تأتي القيمة بصيغتين:

  • رقم موجب — يمثل عدد الصفحات. مع حجم الصفحة الافتراضي 4 كيلوبايت، فإن 2000 يساوي 8 ميجابايت.
  • رقم سالب — يمثل الكيبيبايت. القيمة -20000 تعني 20 ميجابايت بغض النظر عن حجم الصفحة.

الصيغة بالسالب أسهل في الفهم — فأنت تقول ببساطة "أعطني 20 ميجابايت كاش" بدل أن تحسب بناءً على حجم الصفحة. بالنسبة لتطبيق صغير، يكفيك ما بين 20 و50 ميجابايت. أما إذا كان عبء العمل قراءة مكثفة على قاعدة بيانات كبيرة، فارفع القيمة أكثر. ومثل synchronous، فإن cache_size يُضبط لكل اتصال على حدة.

mmap_size

الإدخال والإخراج المعتمد على تخطيط الذاكرة (Memory-mapped I/O) يسمح لـ SQLite بقراءة أجزاء من ملف قاعدة البيانات مباشرةً من ذاكرة الصفحات في نظام التشغيل، متجاوزًا عملية النسخ. هذا يُسرّع القراءة بشكل ملحوظ على قواعد البيانات الكبيرة:

هذا يعادل 256 ميغابايت. سيقوم SQLite بتحميل (mapping) ما يصل إلى هذا الحجم من قاعدة البيانات داخل الذاكرة إن توفّرت المساحة. نظام التشغيل هو من يتولّى إدارة الصفحات (paging)، فأنت لا تحجز 256 ميغابايت دفعة واحدة، بل تسمح فقط بالوصول إلى هذا الحدّ الأقصى.

تظهر فائدة mmap_size بوضوح مع أحمال العمل التي يغلب عليها القراءة، كما أنه آمن تمامًا مع قواعد البيانات الصغيرة. القيم الافتراضية متحفّظة، لذا فإن رفع هذه القيمة يكون في الغالب مكسبًا صافيًا لتحسين أداء SQLite.

PRAGMA optimize

يعتمد مخطِّط الاستعلامات (query planner) على الإحصائيات لاختيار الفهارس المناسبة. وحين تصبح هذه الإحصائيات قديمة، تأتي الخطط التنفيذية سيّئة. يقوم PRAGMA optimize بتحديث تلك الإحصائيات بتكلفة زهيدة:

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

وهو يختلف عن ANALYZE، الذي يُعيد بناء الإحصائيات بالكامل. أما optimize فهو القريب الخفيف الذي يمكنك تشغيله بشكل متكرر.

قراءة جميع الإعدادات الحالية

إذا أردت معرفة الإعدادات الحالية للاتصال، فاستعلم عن قيم PRAGMA دون إسناد قيمة جديدة:

مفيد جدًا أثناء تصحيح الأخطاء — لو اتصلت بقاعدة البيانات من تعريف مختلف ولاحظت أن السلوك تغيّر، فالسبب في الغالب هو اختلاف في إعدادات PRAGMA.

كذلك يوجد الأمر PRAGMA pragma_list; الذي يعرض لك جميع إعدادات PRAGMA المدعومة في النسخة المُجمَّعة لديك:

PRAGMA pragma_list;

ليس من النوع الذي تحفظه عن ظهر قلب، لكنه يفيدك وقت الحاجة.

إعدادات تنتمي إلى مرحلة الإنشاء، لا إلى وقت التشغيل

هناك بعض إعدادات PRAGMA التي تخصّ ملف قاعدة البيانات نفسه، ولا تأخذ مفعولها إلا قبل إنشاء أي جدول:

  • PRAGMA page_size = 8192; — حجم الصفحة على القرص. القيمة الافتراضية هي 4096، وهي مناسبة لأغلب الاستخدامات. الصفحات الأكبر تفيد عند التعامل مع صفوف ضخمة.
  • PRAGMA encoding = 'UTF-8'; — ترميز النصوص.
PRAGMA page_size = 8192;
PRAGMA encoding = 'UTF-8';
CREATE TABLE ...

إذا غيّرت page_size على قاعدة بيانات موجودة، فلازم تشغّل VACUUM حتى يسري التغيير. اضبط هذه الإعدادات مرة واحدة عند إنشاء القاعدة، وانساها بعد ذلك.

مقطع عملي لإعداد الاتصال

في كود التطبيق الفعلي، عادةً ما توضع هذه الإعدادات في المكان الذي يفتح الاتصال بقاعدة البيانات. والفكرة باختصار:

-- يتم تشغيله مرة واحدة عند كل اتصال جديد:
PRAGMA journal_mode = WAL;
PRAGMA synchronous = NORMAL;
PRAGMA foreign_keys = ON;
PRAGMA busy_timeout = 5000;
PRAGMA cache_size = -20000;
PRAGMA temp_store = MEMORY;

-- يتم تشغيله بشكل دوري، أو قبل الإغلاق:
PRAGMA optimize;

temp_store = MEMORY يحتفظ بالجداول والفهارس المؤقتة داخل الذاكرة، مما يُسرّع الاستعلامات التي تحتاج إلى فرز أو تجميع بدون فهرس.

هذه هي قائمة ضبط SQLite للإنتاج كاملةً. ستة أسطر فقط، وينتقل SQLite من "مناسب للتطوير" إلى "جاهز لأحمال العمل الحقيقية".

التالي: الأخطاء الشائعة

حتى مع ضبط إعدادات PRAGMA بشكل جيد، ستصادفك أخطاء SQLite المعتادة — database is locked وdisk I/O error وconstraint failed. الصفحة التالية تشرح ما يعنيه كل خطأ منها فعليًا وكيفية معالجته.

الأسئلة الشائعة

ما هي أوامر PRAGMA في SQLite؟

أوامر PRAGMA هي تعليمات خاصة بـ SQLite تستخدمها لقراءة سلوك محرك قاعدة البيانات أو تعديله. تُنفَّذ مثل أي أمر SQL عادي؛ فمثلاً PRAGMA journal_mode = WAL; يغيّر وضع الـ journaling، وPRAGMA foreign_keys; يقرأ القيمة الحالية. معظم هذه الأوامر تعمل على مستوى الاتصال (connection) فقط، ولذلك يُفضَّل تنفيذها مباشرة بعد فتح قاعدة البيانات.

ما هي إعدادات PRAGMA المناسبة لبيئة الإنتاج؟

هناك مجموعة آمنة وصالحة لمعظم التطبيقات: journal_mode = WAL، وsynchronous = NORMAL، وforeign_keys = ON، وbusy_timeout = 5000، مع قيمة سخية لـ cache_size. ويُستحسن تنفيذ PRAGMA optimize قبل إغلاق الاتصالات طويلة العمر. هذه الإعدادات تمنحك قراءات متزامنة وكتابات موثوقة وسلامة مرجعية للبيانات دون تعقيد يُذكر.

لماذا foreign_keys معطّل افتراضياً في SQLite؟

السبب هو التوافق مع الإصدارات السابقة. أضاف SQLite دعم تطبيق المفاتيح الأجنبية في الإصدار 3.6.19، وأبقاه معطّلاً افتراضياً حتى لا تبدأ قواعد البيانات القديمة فجأة برفض عمليات الكتابة. لذلك تحتاج إلى تفعيله بنفسك عبر PRAGMA foreign_keys = ON; مع كل اتصال جديد، فهو إعداد على مستوى الاتصال وليس على مستوى قاعدة البيانات.

ماذا يفعل PRAGMA optimize؟

ينفّذ PRAGMA optimize عمليات صيانة خفيفة، أبرزها تحديث الإحصاءات التي يعتمد عليها مخطّط الاستعلامات (query planner) في اختيار الفهارس المناسبة. هو رخيص وآمن ويمكن تشغيله بشكل دوري. والنمط الموصى به هو استدعاؤه قبل إغلاق الاتصالات طويلة العمر مباشرة، حتى تكون الإحصاءات محدّثة عند بدء التطبيق في المرة التالية.

Coddy programming languages illustration

تعلّم البرمجة مع Coddy

ابدأ الآن