افهم نموذج القفل في SQLite، ومعاملاتها، وشذوذات العزل التي يصفها معيار SQL.
افتح هذا الدرس في Kodokonتقفل SQLite على مستوى ملف قاعدة البيانات بأكمله، لا على مستوى الصف. في الوضع الافتراضي (سجل التراجع rollback journal)، يتشارك القارئون قفلًا SHARED، لكن كاتبًا واحدًا فقط في كل مرة يستطيع الحصول على القفل EXCLUSIVE. والنتيجة: تُسلسَل عمليات الكتابة. لننشئ جدولًا للعمل مع المعاملات.
CREATE TABLE accounts (
id INTEGER PRIMARY KEY,
balance INTEGER NOT NULL
);
INSERT INTO accounts (id, balance)
VALUES (1, 100), (2, 50);
-- Read: deferred transaction (default)
BEGIN;
SELECT balance FROM accounts WHERE id = 1;
COMMIT;يتطوّر القفل على مراحل: UNLOCKED، ثم SHARED (قراءة)، وRESERVED (نية الكتابة)، وPENDING، وأخيرًا EXCLUSIVE (كتابة). فإذا كانت معاملة تحمل بالفعل قفل الكتابة، تتلقّى أخرى الخطأ SQLITE_BUSY. وبدلًا من الفشل فورًا، حدِّد مهلة انتظار باستخدام busy_timeout، وفعِّل وضع WAL للسماح للقارئين والكاتب بالتقدّم على التوازي.
-- A single writer, concurrent readers
PRAGMA journal_mode = WAL;
-- Wait up to 5 s if the database is busy
PRAGMA busy_timeout = 5000;يصف معيار SQL ثلاثة شذوذات: القراءة القذرة (قراءة بيانات غير مُثبَّتة)، والقراءة غير القابلة للتكرار (إعادة قراءة صف ورؤية قيمة مختلفة)، والقراءة الشبحية (إعادة تنفيذ استعلام على نطاق ورؤية صفوف جديدة تظهر). ولأن SQLite تُسلسِل الكُتّاب، فإنها تتصرّف فعليًّا كأنها SERIALIZABLE: فلا تظهر هذه الشذوذات (خارج وضع الذاكرة المؤقتة المشتركة مع read_uncommitted). وفي غير ذلك، تختار المستوى صراحةً.
-- Atomic transfer: immediate write lock
BEGIN IMMEDIATE;
UPDATE accounts
SET balance = balance - 10
WHERE id = 1;
UPDATE accounts
SET balance = balance + 10
WHERE id = 2;
COMMIT;BEGIN وBEGIN IMMEDIATE؟