RELEASE الداخلية لا تمنع التراجع الخارجي، وROLLBACK TO تترك النقطة المستهدفة قائمة. نختبر الفرق بثلاث قواعد صغيرة. [1]
الخلاصة السريعة
- في A رأينا الصفين 1 و2 بعد RELEASE، ثم لم نجد أي صف بعد ROLLBACK.
- في B رجعنا إلى الصف 1 مرتين، وبقي بعد الإغلاق وإعادة الفتح.
- في C بقي الصف 9 بعد RELEASE وإعادة الفتح.
- نفذنا كل حالة في ملف جديد؛ لا نعزو نتيجة ملف إلى حالة أخرى.
ثلاثة ملفات وصف واحد قبل كل فرع
أنشأنا لكل حرف ملف قاعدة مستقلا وجدولا واحدا اسمه t، له عمود n. الأعداد 1 و2 و3 و9 معرفات صفوف في تمريننا؛ ليست مبالغ ولا قياسات. جعلنا A وB تبدأان بجدول فارغ، ثم سجلنا الرقم 1 قبل وضع نقطة الحفظ s.
نفذنا التجربة في Python 3.12.14 مع SQLite 3.53.1. سجلنا نتيجة كل SELECT، ثم أغلقنا الاتصال وفتحنا الملف نفسه باتصال جديد. لا يكفي فحص متغير يحتفظ بنتيجة سابقة؛ نتيجة خانة «بعد الفتح» جاءت من قراءة جديدة للملف الذي أنشأته تلك الحالة.
A: ظهر الصفان ثم اختفيا
أدخلنا 1، ووضعنا s، ثم أدخلنا 2. بعد RELEASE s أظهر استعلامنا الصفين 1 و2. لذلك لم نفسر اختفاء اسم النقطة من التعليمات اللاحقة بأنه حذف للصف الثاني في تلك اللحظة.
تابعنا الحالة نفسها بأمر ROLLBACK بلا TO. صار ناتج SELECT قائمة فارغة، ثم بقي فارغا عند إعادة فتح ملف A. لو اكتفينا بالقراءة التي سبقت هذا الأمر لقلنا إن الصفين موجودان، وهو وصف صحيح لتلك اللحظة وحدها في سجلنا.
B: عودة ثانية إلى الرقم 1
عدنا إلى ملف آخر فارغ، وأدخلنا 1 قبل s ثم 2 بعدها. نفذنا ROLLBACK TO s فقرأنا 1 وحده. أضفنا 3، وكررنا الأمر نفسه، فقرأنا 1 مرة أخرى. وجود المحاولة الثانية في سجل التنفيذ يميز هذا الفرع عن تجربة تنتهي عند أول رجوع.
بعد القراءتين نفذنا RELEASE s ثم COMMIT، وأغلقنا الملف. أعاد الاتصال الجديد الصف 1 وحده؛ لم يظهر 2 أو3. في الرسم الداخلي نضع نقطتي القياس بجانب بعضهما حتى لا تبدو القراءة الثانية نسخة من خانة الأولى.

C: النقطة تبدأ المعاملة
في الملف الثالث بدأنا مباشرة بـSAVEPOINT s، ثم أدخلنا 9 وأطلقنا RELEASE s. لم نضع BEGIN في هذا الفرع. بعد إغلاق الاتصال وإعادة فتحه قرأنا 9؛ هذه هي النتيجة الثالثة في جدولنا، وليست تكرارا للفرع A الذي احتوى BEGIN قبل النقطة.
إطلاق النقطة الخارجية الأخيرة يعادل COMMIT؛ أما النقطة الداخلية فتظل ضمن المعاملة الخارجية. [1]
| الحالة | قراءة داخل التنفيذ | بعد إغلاق الملف وفتحه |
|---|---|---|
| A | 1،2 ثم قائمة فارغة | قائمة فارغة |
| B | 1 ثم1 بعد الرجوعين | 1 |
| C | إدخال9 ثم RELEASE | 9 |
شغل النسخ الثلاث في مجلد جديد
نثبت LEGACY_TRANSACTION_CONTROL وisolation_level=None لنمنع الفتح الضمني؛ نتحكم بالمعاملات بأوامر SQL الصريحة. [2]
شغل الشيفرة باستخدام Python 3.12 داخل مجلد تجريبي؛ تنشئ sqlite-demo، وتتوقف إن كان موجودا. [2]
الشيفرة المعروضة هي التي أعدنا تشغيلها، وأعادت قراءات الجدول. علامة reopened في الخرج تحدد القراءة التي تمت بعد إغلاق الاتصال الأول. احتفظنا أيضا بفحص منفصل يرتب أعداد الصفوف قبل مقارنتها؛ ترتيب عرض القيم ليس السؤال الذي نختبره هنا.
import sqlite3
from pathlib import Path
runs = {
"A": ["BEGIN", "INSERT INTO t VALUES(1)",
"SAVEPOINT s", "INSERT INTO t VALUES(2)",
"RELEASE s", "SELECT n FROM t",
"ROLLBACK", "SELECT n FROM t"],
"B": ["BEGIN", "INSERT INTO t VALUES(1)",
"SAVEPOINT s", "INSERT INTO t VALUES(2)",
"ROLLBACK TO s", "SELECT n FROM t",
"INSERT INTO t VALUES(3)", "ROLLBACK TO s",
"SELECT n FROM t", "RELEASE s", "COMMIT"],
"C": ["SAVEPOINT s", "INSERT INTO t VALUES(9)",
"RELEASE s"]
}
root = Path("sqlite-demo")
root.mkdir()
for name, commands in runs.items():
path = root / (name + ".db")
c = sqlite3.connect(path, isolation_level=None,
autocommit=sqlite3.LEGACY_TRANSACTION_CONTROL)
c.execute("CREATE TABLE t(n)")
for sql in commands:
rows = c.execute(sql).fetchall()
if sql.startswith("SELECT"):
print(name, rows)
c.close()
c = sqlite3.connect(path)
print(name, "reopened", c.execute("SELECT n FROM t").fetchall())
c.close()اقرأ السجل إلى آخره
راقب ثلاث خانات في تجربة A: بعد إطلاق النقطة، وبعد الرجوع الخارجي، وبعد إعادة الفتح. اختلفت الأولى عن الأخيرتين. وفي B راقب موضع إضافة 3 بين الرجوعين؛ لو وضعتها بعد الرجوع الثاني لغيرت تسلسل التجربة، ولن يكون جدولنا وصفا لذلك التسلسل الجديد.
لم نقطع الكهرباء ولم نشغل اتصالين متزامنين، ولم نفحص استعادة نسخة احتياطية. لذلك نعرض نتائج الأوامر المحلية وإعادة الفتح فقط. قيمة هذا التمرين أن يبقى اسم الملف وسطر الأمر والصف المقروء متجاورة؛ لا تمنح كلمة «حفظ» وحدها دليلا على نتيجة لم نقرأها.
المراجع
- SQLite — Savepoints
https://www.sqlite.org/lang_savepoint.html - Python — sqlite3
https://docs.python.org/3.12/library/sqlite3.html
أُعدّ هذا المقال بمساعدة الذكاء الاصطناعي وراجعته أدوات ذكاء اصطناعي. لا يُنسب إلى مراجع بشري أو خبير مستقل. منهجية المحتوى.
