SQLite / المتقدم: التزامن والاسترداد وحدود التطبيق / الدرس 18
عقود تطبيقات مضمّنة مرنة
اجمع سياسة الاتصال وجاهزية المخطط والتغييرات المحروسة وهوية الطلب الدائمة والفشل القابل للرصد في حد تطبيقي صغير لـ SQLite.
في هذه الصفحة
أهداف الدرس
- تعريف سياسة واحدة لملكية الاتصال والمعاملة.
- التوفيق بين المحاولات وهوية عملية دائمة.
- التحقق من الجاهزية بما يتجاوز نجاح استدعاء الفتح.
اجعل سلوك التخزين جزءاً من واجهة التطبيق
ينبغي أن يحدد حد البيانات المضمّن الملف وبيئة التشغيل وخيارات الاتصال وإصدار المخطط وملكية المعاملة وربط المعاملات والتخويل وحدود الموارد وتحويل الأخطاء. تترك دالة تعيد اتصالاً مفتوحاً فقط مسؤوليات غير محسومة. ولا ينبغي أن يختار المستدعون سياسات مفاتيح أجنبية أو معاملات مختلفة خفية للبيانات التجارية نفسها.
قيد الاتصالات بوحدات موثقة واحترم قواعد خيوط مشغل المضيف. لا ينشئ تعطيل فحص الخيط نفسه في الغلاف مزامنة صحيحة أو ملكية معاملات. يهم نمط SQLite المهيأ وتكامل المضيف معاً. اختبر ملكية التطبيق الفعلية، ولا تفترض أن «SQLite آمن مع الخيوط» يجيز استعمال مقبض واحد بالتزامن بلا قيود.
افحص بيانات المخطط الوصفية وسلامته الفعلية منفصلتين
ينشئ الأول سياسة مفاتيح أجنبية ومخططاً مقيداً وبيانات إصدار، ويعرض الإعداد والعلامة وفحص العلاقات والفحص البنيوي السريع. تعني foreign_key_check الفارغة عدم رصد انتهاك بالمثال، لا تحقق كل قاعدة عمل. تفيد user_version كبوابة، ويجب مقارنتها بالمخطط والترحيلات المتوقعين فعلياً.
تشمل الجاهزية أذونات التخزين والسعة والامتدادات والدوال المطلوبة وتوافق المستهلكين. قد ينشئ الفتح الناجح ملفاً فارغاً جديداً ببعض واجهات المضيف؛ فلا تخلطه بفتح القاعدة القائمة المقصودة. استخدم سياسة مسار ووضع صريحة وهوية تطبيق معلومة عند الحاجة، ولا تستطلع ملفات مهيأة فعلياً ببرنامج تدريبي.
PRAGMA foreign_keys=ON;
CREATE TABLE customer(id INTEGER PRIMARY KEY) STRICT;
CREATE TABLE purchase(id INTEGER PRIMARY KEY,customer_id INTEGER NOT NULL REFERENCES customer(id)) STRICT;
INSERT INTO customer VALUES(1);
INSERT INTO purchase VALUES(10,1);
PRAGMA user_version=1;
PRAGMA foreign_keys;
PRAGMA user_version;
PRAGMA foreign_key_check;
PRAGMA quick_check;
foreign_keys
1
user_version
1
table|rowid|parent|fkid
(no rows)
quick_check
ok
يعرض فرض المفاتيح الأجنبية وuser_version القيمة 1، ولا يعيد فحص العلاقات صفوفاً، ويعيد الفحص البنيوي المصطنع ok.
امنح المحاولات هوية الطلب المقصود نفسه
يمثل مفتاح طلب فريد ودائم عملية مقصودة واحدة عبر المحاولات. احفظ الحمولة المتحقق منها ونتيجتها مع تعديل المخزون المحروس بمعاملة واحدة. عند التكرار افحص التكافؤ وأعد الأصل، وارفض إعادة الاستخدام لعملية أخرى. لا يكفي فحص وجود تمهيدي دون تفرد وتنسيق معاملات عند التزامن.
يملك الثاني معاملته ويستخدم BEGIN IMMEDIATE للعملية الكتابية المحدودة. يقبل طلب الوحدتين، ويعيد النتيجة نفسها عند تكراره، ويرفض كمية مختلفة بالمفتاح. هذا عقد مصطنع لمنتج واحد؛ يجب أن تشمل الهوية الحقيقية المنتج ونطاق المستأجر الموثوق وكل حقل يحدد العملية، لا المفتاح المبسط وحده.
import sqlite3
from contextlib import closing
def reserve(db, request_key, quantity):
if type(quantity) is not int or quantity <= 0:
raise ValueError("quantity")
db.execute("BEGIN IMMEDIATE")
try:
prior = db.execute("SELECT quantity,result FROM request_log WHERE request_key=?", (request_key,)).fetchone()
if prior is not None:
if prior[0] != quantity:
raise ValueError("request payload differs")
db.execute("COMMIT")
return prior[1]
row = db.execute("UPDATE stock SET quantity=quantity-? WHERE id=1 AND quantity>=? RETURNING quantity",
(quantity, quantity)).fetchone()
if row is None:
raise ValueError("insufficient stock")
db.execute("INSERT INTO request_log VALUES(?,?,?)", (request_key, quantity, row[0]))
db.execute("COMMIT")
return row[0]
except Exception:
if db.in_transaction:
db.execute("ROLLBACK")
raise
with closing(sqlite3.connect(":memory:", isolation_level=None)) as db:
db.executescript("CREATE TABLE stock(id INTEGER PRIMARY KEY,quantity INTEGER NOT NULL CHECK(quantity>=0)); INSERT INTO stock VALUES(1,5); CREATE TABLE request_log(request_key TEXT PRIMARY KEY NOT NULL,quantity INTEGER NOT NULL,result INTEGER NOT NULL);")
print("first", reserve(db, "training-1", 2))
print("replay", reserve(db, "training-1", 2))
try:
reserve(db, "training-1", 3)
except ValueError:
print("conflict rejected")
print("stock", db.execute("SELECT quantity FROM stock").fetchone()[0])
print("requests", db.execute("SELECT COUNT(*) FROM request_log").fetchone()[0])
first 3
replay 3
conflict rejected
stock 3
requests 1
ينخفض المخزون مرة واحدة من 5 إلى 3. تعيد المحاولة المطابقة 3، وتُرفض الحمولة المختلفة، ويخزن صف طلب واحد فقط.
لا تعتبر كل استجابة مفقودة تراجعاً عن المعاملة
قد تلتزم القاعدة رغم خطأ المستدعي إذا ضاع التأكيد قرب الالتزام. صالح النتيجة بمفتاح العملية الأصلي، لا مفتاح جديد يكرر الفعل التجاري. يختلف التراجع المؤكد لمعاملة مرفوضة عن الإكمال المجهول؛ لتميزهما سياسة الإعادة مع محاولات محدودة مناسبة.
لا يعكس تراجع SQLite الآثار الخارجية. ينسق صف outbox الملتزم مع الحالة التسليم اللاحق، ويحتاج المستهلك إزالة التكرار لاحتمال إعادة التسليم. لا تحصّل رسوماً أو تخطر أو تعدل خدمة داخل دالة تعاد محاولتها دون عقد تنسيق مراجع. لا تنفذ الأمثلة خارجياً ولا تضمن شبكة تنفذ مرة واحدة تماماً.
اختبر حدود التطبيق كاملة
تحقق من التعديل العادي والإعادة المطابقة والحمولة المختلفة ونفاد المخزون ورفض القيد وفشل الانشغال والإلغاء واختلاف الترحيل وإغلاق الموارد. احفظ خرجاً دقيقاً للحساب المحلي الحتمي وجداول مضبوطة للتزامن. لا يعتمد محلل النتائج على نص EQP غير المستقر أو rowid بوصفه دليل نجاح.
سجل متطلبات الميزات والتصحيحات والنسخ وتجارب الاستعادة وحماية البيانات مع التطبيق. احفظ هوية العملية وأسباباً مقيدة الاطلاع دون اعتماد أو سجلات شخصية كاملة. يشرح التكامل المرن المخزن ومن يغيره ومتى يدوم وكيف يستعاد وكيف يعرف المستدعي النتيجة الصادقة، لا نجاح SQL وحده.
الأخطاء الشائعة وإصلاحها
حجزت إعادة المحاولة المخزون مرتين.
- لماذا يحدث؟
- أعيد الإكمال غير المؤكد باعتباره عملية جديدة.
- كيف تصلحه؟
- صالح النتيجة باستخدام مفتاح الطلب الدائم الأصلي وحمولته.
- كيف تتجنب تكراره؟
- اختبر فقد التأكيد وإعادة الطلب المطابقة.
اعتُبرت علامة الإصدار جاهزية كاملة.
- لماذا يحدث؟
- لم يفحص المخطط الفعلي أو السياسة أو السلامة.
- كيف تصلحه؟
- تحقق من الكائنات المتوقعة والمتطلبات التشغيلية.
- كيف تتجنب تكراره؟
- اختبر الملف الخطأ واختلاف الترحيل.
أدى تعطيل فحص الخيط إلى أخطاء معاملات مربكة.
- لماذا يحدث؟
- أزيل حارس الغلاف بلا تصميم ملكية ومزامنة.
- كيف تصلحه؟
- استخدم اتصالاً لكل وحدة وفق توثيق، أو سياسة مراجعة لمشاركة المقبض.
- كيف تتجنب تكراره؟
- اختبر نمط الخيوط في المضيف الفعلي.
اختبر فهمك
1. وصل المفتاح نفسه بكمية مختلفة. لماذا يرفض المثال الطلب بدلاً من إعادة النتيجة المخزنة؟
اعرض الحل المشروح
يفترض أن يحدد المفتاح العملية المقصودة نفسها. تتعارض حمولة مختلفة مع هذا الوعد، وقد تخفي خطأ في المستدعي أو طلباً آخر. تحقق من كل حقل يحدد العملية قبل اعتبار المكرر إعادة آمنة لا تضيف أثراً؛ فلا يكفي تطابق المفتاح النصي وحده.
2. انتهت مهلة كتابة مع غموض تأكيد الالتزام. هل ينشئ المستدعي مفتاح طلب جديداً؟
اعرض الحل المشروح
لا. احفظ الهوية الأصلية وصالح نتيجتها الدائمة عبر مسار التعافي المدعوم. قد ينشئ المفتاح الجديد عملية مقبولة ثانية بعد التزام الأولى فعلاً. أبق المهلة والتراجع والإكمال المجهول نتائج منفصلة، واختر الإجراء من الدليل المتاح لكل منها.
أهم ما تعلمته
- تشكل سياسات الاتصال والمعاملة والمخطط والأمان حداً واحداً.
- تجعل هوية العملية الدائمة المحاولات قابلة للمصالحة.
- تهم النتيجة الصادقة وإمكان الاستعادة بما يتجاوز نجاح تنفيذ SQL.