تخطَّ إلى المحتوى

المعاملات في SQL: BEGIN و COMMIT و ROLLBACK

الدرس 12 من 12 · SQL

تحويل 100 جنيه من حساب سارة إلى حساب عمر عمليتان: خصم من الأول، وإضافة إلى الثاني. ماذا لو انقطعت الكهرباء بينهما؟ خُصم المبلغ من سارة ولم يصل إلى عمر، واختفت 100 جنيه من الوجود! لحل هذه المشكلة توجد المعاملات (Transactions): مجموعة أوامر تُنفَّذ كوحدة واحدة، إما أن تنجح كلها أو كأن شيئاً لم يكن.

CREATE TABLE accounts (
id INTEGER PRIMARY KEY,
owner TEXT NOT NULL,
balance INTEGER NOT NULL CHECK (balance >= 0)
);
INSERT INTO accounts (id, owner, balance) VALUES (1, 'سارة', 500), (2, 'عمر', 200);

لاحظ القيد CHECK (balance >= 0): قاعدة البيانات نفسها ترفض أي رصيد سالب.

BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT;
SELECT * FROM accounts;

الناتج:

id owner balance
1 سارة 400
2 عمر 300
  • BEGIN: ابدأ معاملة. كل ما بعدها تغييرات مؤقتة لم تُعتمد بعد.
  • COMMIT: اعتمد كل التغييرات معاً واحفظها نهائياً.

إذا تعطل الخادم في أي لحظة قبل COMMIT، تتراجع قاعدة البيانات تلقائياً عن كل ما حدث منذ BEGIN. لا توجد حالة «نصف تحويل».

المعاملة مثل عربة التسوق في متجر إلكتروني: تضيف وتحذف كما تشاء ولا شيء يُحسب عليك، حتى تضغط «إتمام الشراء». عندها فقط تُعتمد كل المشتريات دفعة واحدة.

ROLLBACK تلغي كل ما حدث منذ BEGIN وتعيد البيانات كما كانت:

BEGIN;
UPDATE accounts SET balance = balance - 300 WHERE id = 1;
SELECT * FROM accounts;

الناتج:

id owner balance
1 سارة 100
2 عمر 300

داخل المعاملة نرى التغيير: رصيد سارة 100. والآن نتراجع:

ROLLBACK;
SELECT * FROM accounts;

الناتج:

id owner balance
1 سارة 400
2 عمر 300

عاد الرصيد إلى 400 كأن التعديل لم يحدث.

عندما يفشل أمر في المنتصف

Section titled “عندما يفشل أمر في المنتصف”

لنحاول تحويل 900 جنيه من سارة، ورصيدها 400 فقط:

BEGIN;
UPDATE accounts SET balance = balance + 900 WHERE id = 2;
UPDATE accounts SET balance = balance - 900 WHERE id = 1;
الناتج
CHECK constraint failed: balance >= 0

الأمر الأول نجح وأضاف 900 إلى عمر، والثاني فشل بسبب القيد. لو توقفنا هنا واعتمدنا، لحصل عمر على مال من لا شيء. لذلك نتراجع عن كل شيء:

ROLLBACK;
SELECT * FROM accounts;

الناتج:

id owner balance
1 سارة 400
2 عمر 300

هذا هو النمط الذي تتبعه كل البرامج الحقيقية: ابدأ معاملة، نفّذ الأوامر، وإذا فشل أي أمر فتراجع عن الكل، وإلا فاعتمد.

شبكة أمان من الأخطاء البشرية

Section titled “شبكة أمان من الأخطاء البشرية”

المعاملات تحميك من نفسك أيضاً. أشهر كارثة: DELETE بلا WHERE.

BEGIN;
DELETE FROM accounts;
SELECT COUNT(*) AS remaining FROM accounts;

الناتج:

remaining
0

حُذفت كل الحسابات! لكننا داخل معاملة:

ROLLBACK;
SELECT COUNT(*) AS remaining FROM accounts;

الناتج:

remaining
2

أحياناً تريد التراجع عن جزء من المعاملة فقط. SAVEPOINT تضع علامة تعود إليها:

BEGIN;
INSERT INTO accounts (id, owner, balance) VALUES (3, 'منى', 50);
SAVEPOINT before_bonus;
UPDATE accounts SET balance = balance + 1000;
ROLLBACK TO before_bonus;
COMMIT;
SELECT * FROM accounts;

الناتج:

id owner balance
1 سارة 400
2 عمر 300
3 منى 50

المكافأة الخاطئة أُلغيت بـ ROLLBACK TO، لكن إضافة حساب منى بقيت واعتُمدت. نقطة الحفظ مثل نقطة الحفظ في لعبة: تعود إليها دون أن تخسر كل ما قبلها.

إذا لم تكتب BEGIN، تعمل قاعدة البيانات في وضع الاعتماد التلقائي (Autocommit): كل أمر معاملة مستقلة تُعتمد فور نجاحها. لهذا كانت أوامر INSERT و UPDATE في الدروس السابقة تُحفظ مباشرة، ولهذا أيضاً لا يمكن التراجع عنها.

المعاملات تضمن أربع خصائص، تُختصر بالحروف ACID:

الخاصية المعنى
Atomicity (الذرّية) الكل أو لا شيء: لا تنفيذ جزئياً
Consistency (الاتساق) البيانات تنتقل من حالة صحيحة إلى حالة صحيحة، والقيود محترمة دائماً
Isolation (العزل) المعاملات المتزامنة لا ترى تغييرات بعضها غير المعتمدة
Durability (الدوام) ما اعتُمد بـ COMMIT يبقى، حتى لو انقطعت الكهرباء بعدها بلحظة

خاصية العزل هي ما يسمح لآلاف المستخدمين بالشراء من متجر واحد في اللحظة نفسها دون أن يُباع آخر منتج مرتين.

  1. أنشئ جدول accounts بالبيانات السابقة.
  2. نفّذ معاملة تحوّل 50 من عمر إلى سارة واعتمدها، ثم تحقق من الأرصدة.
  3. ابدأ معاملة، وضاعف كل الأرصدة، واعرضها، ثم تراجع وتأكد أنها عادت.
  4. تحدٍّ: في معاملة واحدة أضف حسابين جديدين مع SAVEPOINT بينهما، ثم تراجع عن الثاني فقط واعتمد الأول.
  • نسيان COMMIT: التغييرات تبقى معلّقة، وإذا أُغلق الاتصال تضيع كلها. وفي أنظمة أخرى تبقى الصفوف مقفلة أمام المستخدمين الآخرين.
  • معاملة داخل معاملة: كتابة BEGIN مرتين تعطي في SQLite: cannot start a transaction within a transaction. استخدم SAVEPOINT للتراجع الجزئي.
  • معاملات طويلة: إبقاء المعاملة مفتوحة أثناء انتظار المستخدم يعطّل الآخرين. اجعلها قصيرة: ابدأ، نفّذ، اعتمد.
  • الاعتماد على ROLLBACK بعد أوامر البنية: في بعض الأنظمة مثل MySQL، أوامر مثل CREATE TABLE و DROP TABLE تُعتمد فوراً ولا يمكن التراجع عنها.

سؤال

نفّذت BEGIN ثم أمرَي UPDATE ثم انقطع الاتصال قبل COMMIT. ماذا يحدث للتعديلين؟

سؤال

ما وظيفة ROLLBACK TO name بعد SAVEPOINT name؟

  • المعاملة مجموعة أوامر تنجح كلها أو تُلغى كلها.
  • BEGIN تبدأها، و COMMIT تعتمدها، و ROLLBACK تتراجع عنها.
  • عند فشل أي أمر داخل المعاملة أرسل ROLLBACK، و SAVEPOINT للتراجع الجزئي.
  • بدون BEGIN كل أمر يُعتمد تلقائياً ولا رجعة فيه.
  • مبروك! أنهيت كورس SQL: من أول SELECT إلى الفهارس والمعاملات، وهي أدوات تعمل مع كل قواعد البيانات العلائقية.