نقطة الحفظ: علامة مرجعية باسم داخل المعاملة
المعاملة العادية في SQLite تعمل بمبدأ "الكل أو لا شيء"؛ فكل ما يقع بين BEGIN و COMMIT إمّا يُحفظ دفعة واحدة وإمّا يُلغى دفعة واحدة. هذا السلوك جيد في معظم الحالات، لكنك أحياناً تحتاج إلى تحكم أدق: "جرّب هذه المجموعة من التغييرات، فإن حدث خطأ تراجع عنها وحدها واترك بقية المعاملة شغّالة".
هنا يأتي دور نقاط الحفظ في SQLite. تضع علامة مرجعية باسم معيّن، تنفّذ ما تريد، ثم إمّا أن تُثبّت العمل عبر RELEASE، أو ترجع إلى تلك العلامة عبر ROLLBACK TO.
يبقى الخصم من حساب Ada والإضافة لحساب Boris كما هما، أمّا التحديث الخاطئ على Nobody فقد تم التراجع عنه دون أن نخسر باقي المعاملة.
الأوامر الثلاثة
كل ما تحتاجه يتلخّص في ثلاث تعليمات فقط:
SAVEPOINT name— لوضع علامة مرجعية (نقطة حفظ).RELEASE SAVEPOINT name— تثبيت كل ما أُنجز بعد نقطة الحفظ، مع إزالة النقطة نفسها.ROLLBACK TO SAVEPOINT name— التراجع عن كل ما حدث بعد نقطة الحفظ، مع إبقاء النقطة قائمة لتتمكّن من المحاولة مجدّدًا.
كلمة SAVEPOINT بعد RELEASE و ROLLBACK TO اختيارية، فبإمكانك كتابة RELEASE risky أو ROLLBACK TO risky ويعملان بنفس الكفاءة.
صف محاولة الخطوة 2 لم يكن موجودًا أصلًا من وجهة نظر قاعدة البيانات النهائية. أما باقي التغييرات فقد ثُبِّتت كلها.
استخدام SAVEPOINT بدون معاملة خارجية
إليك مفاجأة صغيرة: يمكنك تنفيذ SAVEPOINT دون الحاجة إلى BEGIN قبله. في هذه الحالة، تفتح SQLite معاملةً ضمنية بشكل تلقائي، وتلعب نقطة الحفظ الخارجية دور المعاملة نفسها. عندها يقوم RELEASE على تلك النقطة بتثبيت التغييرات (Commit)، بينما يُرجِع ROLLBACK TO الحالة إلى الوراء دون تثبيت أي شيء.
لهذا السبب يُطلق على نقاط الحفظ أحيانًا اسم "المعاملات المُسمّاة". لكن الخلط بين الأسلوبين داخل كود حقيقي يجلب الفوضى — اختر واحدًا والتزم به. الشائع أن يستخدم المطورون BEGIN ... COMMIT صراحةً للحدود الخارجية، ويتركون نقاط الحفظ لنقاط التراجع الجزئي الداخلية فقط.
نقاط حفظ متداخلة في SQLite
نقاط الحفظ في SQLite قابلة للتكديس؛ بمعنى أنه يمكنك إنشاء نقطة حفظ داخل نقطة حفظ أخرى، والتراجع عن الداخلية دون أن تمسّ الخارجية بأي ضرر:
المحتوى النهائي: a، b، d. لاحظ أن التراجع إلى inner أزال c فقط، بينما بقي العمل المنجز قبل inner (أي إدراج b) كما هو، والمعاملة استمرت في عملها دون انقطاع.
كذلك التراجع إلى نقطة حفظ خارجية يلغي كل ما تم على المستويات الداخلية أيضًا، إذ تنهار كامل المكدس فوق ذلك الاسم دفعة واحدة:
كلٌّ من b وc اختفيا. الأمر ROLLBACK TO outer يُرجِع كل ما حدث منذ تعيين outer، بما في ذلك inner وإدراج c.
لماذا نستخدم نقاط الحفظ في SQLite؟
الحالة الكلاسيكية لاستخدام SAVEPOINT هي معالجة دفعة من العناصر، حيث يُسمح بفشل عنصر واحد دون إلغاء الدفعة كاملة. غلِّف كل عنصر بنقطة حفظ، فإن فشل، تراجَع إلى تلك النقطة وتابع مع البقية:
في الكود الحقيقي للتطبيق، عملية الإدراج الخاطئة سترفع استثناءً، فيلتقطه التطبيق ويُنفّذ ROLLBACK TO ثم يكمل عمله. النتيجة: الصفّان السليمان يُحفظان، والصف الخاطئ لا يُفسد الدفعة كلها.
هذا النمط بالذات هو ما تستخدمه أنظمة ORM وأدوات الـ migrations لتنفيذ ما يُسمى بالمعاملات المتداخلة — فهي لا تُداخل كتل BEGIN فعلياً (لأن SQLite لا يسمح بذلك أصلاً)، بل تُحوّل الاستدعاءات المتداخلة إلى نقاط حفظ (SAVEPOINT).
أمور ينبغي الانتباه إليها
هناك تفاصيل صغيرة كثيراً ما يقع فيها المبتدئون مع نقاط الحفظ في SQLite:
COMMITيُثبّت المعاملة كاملة دائماً. لا يهم كم عدد نقاط الحفظ المفتوحة لديك — تنفيذCOMMIT(أو مرادفهEND) يُغلق المعاملة الخارجية بأكملها. لا تتعامل معRELEASEعلى أنه تثبيت جزئي؛ لا شيء يصير دائماً قبل أن تُثبَّت المعاملة المُحيطة.ROLLBACK(بدونTO) يُلغي كل شيء. فهو يُنهي المعاملة ويتخلّص من جميع نقاط الحفظ المفتوحة. إذا أردت إبقاء المعاملة حيّة، استعملROLLBACK TO name.- نقطة الحفظ تبقى مفتوحة حتى تُحرَّر أو يُتراجَع عبرها. نسيان
RELEASEلا يُضيع البيانات، لكن العلامة تبقى معلّقة إلى أن تنتهي المعاملة. - الأسماء ليست ملزَمة بأن تكون فريدة. إذا عرّفت
SAVEPOINT sمرتين، فإنROLLBACK TO sسيرجع إلى أحدثها. هذا مفيد في حالات الاستدعاء التكراري، لكنه يصبح مربكاً إذا حدث بالخطأ.
الخطوة التالية: العروض (Views)
نقاط الحفظ تمنحك تحكّماً أدقّ في عمليات الكتابة. الخطوة التالية تتعلّق بتشكيل طريقة القراءة — أي حفظ استعلام ككائن مُسمّى قابل لإعادة الاستخدام، تُنفّذ عليه SELECT كأنه جدول. هذا ما يُعرف بـ View، وسنتناوله بعد قليل.
الأسئلة الشائعة
ما هي نقطة الحفظ (Savepoint) في SQLite؟
هي علامة مُسمّاة تضعها داخل المعاملة. لاحقاً يمكنك استخدام ROLLBACK TO مع اسمها للتراجع عن كل ما حدث بعدها، أو RELEASE للإبقاء على التغييرات وحذف العلامة فقط. باختصار، تتيح لك نقاط الحفظ التعامل مع أجزاء من المعاملة كوحدات أصغر قابلة للاسترجاع.
ما الفرق بين نقطة الحفظ والمعاملة في SQLite؟
المعاملة تبدأ بـ BEGIN وتنتهي بـ COMMIT أو ROLLBACK، بينما نقطة الحفظ تُنشأ داخل المعاملة عبر SAVEPOINT name وتعطيك نقطة تراجع جزئية. التراجع إلى نقطة حفظ لا ينهي المعاملة المحيطة بها، فيمكنك المتابعة بالعمل ثم تنفيذ COMMIT لاحقاً.
هل يمكن تداخل نقاط الحفظ في SQLite؟
نعم، يمكنك تكديس عدة نقاط حفظ بأسماء مختلفة، وROLLBACK TO outer يتراجع إلى ذلك المستوى ويُلغي معه كل نقاط الحفظ الداخلية. الأسماء ليست مُلزَمة بأن تكون فريدة؛ إذ يأخذ SQLite أحدث نقطة حفظ تحمل الاسم نفسه، وهو السلوك القياسي للمكدس (Stack).