المعاملات في SQL: BEGIN و COMMIT و ROLLBACK
تحويل 100 جنيه من حساب سارة إلى حساب عمر عمليتان: خصم من الأول، وإضافة إلى الثاني. ماذا لو انقطعت الكهرباء بينهما؟ خُصم المبلغ من سارة ولم يصل إلى عمر، واختفت 100 جنيه من الوجود! لحل هذه المشكلة توجد المعاملات (Transactions): مجموعة أوامر تُنفَّذ كوحدة واحدة، إما أن تنجح كلها أو كأن شيئاً لم يكن.
جدول الحسابات
Section titled “جدول الحسابات”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 و COMMIT
Section titled “BEGIN و COMMIT”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: التراجع
Section titled “ROLLBACK: التراجع”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: تراجع جزئي
Section titled “SAVEPOINT: تراجع جزئي”أحياناً تريد التراجع عن جزء من المعاملة فقط. 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؟
Section titled “بدون BEGIN؟”إذا لم تكتب BEGIN، تعمل قاعدة البيانات في وضع الاعتماد التلقائي (Autocommit): كل أمر معاملة مستقلة تُعتمد فور نجاحها. لهذا كانت أوامر INSERT و UPDATE في الدروس السابقة تُحفظ مباشرة، ولهذا أيضاً لا يمكن التراجع عنها.
خصائص ACID
Section titled “خصائص ACID”المعاملات تضمن أربع خصائص، تُختصر بالحروف ACID:
| الخاصية | المعنى |
|---|---|
| Atomicity (الذرّية) | الكل أو لا شيء: لا تنفيذ جزئياً |
| Consistency (الاتساق) | البيانات تنتقل من حالة صحيحة إلى حالة صحيحة، والقيود محترمة دائماً |
| Isolation (العزل) | المعاملات المتزامنة لا ترى تغييرات بعضها غير المعتمدة |
| Durability (الدوام) | ما اعتُمد بـ COMMIT يبقى، حتى لو انقطعت الكهرباء بعدها بلحظة |
خاصية العزل هي ما يسمح لآلاف المستخدمين بالشراء من متجر واحد في اللحظة نفسها دون أن يُباع آخر منتج مرتين.
جرّب بنفسك
Section titled “جرّب بنفسك”- أنشئ جدول
accountsبالبيانات السابقة. - نفّذ معاملة تحوّل 50 من عمر إلى سارة واعتمدها، ثم تحقق من الأرصدة.
- ابدأ معاملة، وضاعف كل الأرصدة، واعرضها، ثم تراجع وتأكد أنها عادت.
- تحدٍّ: في معاملة واحدة أضف حسابين جديدين مع
SAVEPOINTبينهما، ثم تراجع عن الثاني فقط واعتمد الأول.
أخطاء شائعة
Section titled “أخطاء شائعة”- نسيان COMMIT: التغييرات تبقى معلّقة، وإذا أُغلق الاتصال تضيع كلها. وفي أنظمة أخرى تبقى الصفوف مقفلة أمام المستخدمين الآخرين.
- معاملة داخل معاملة: كتابة
BEGINمرتين تعطي في SQLite:cannot start a transaction within a transaction. استخدمSAVEPOINTللتراجع الجزئي. - معاملات طويلة: إبقاء المعاملة مفتوحة أثناء انتظار المستخدم يعطّل الآخرين. اجعلها قصيرة: ابدأ، نفّذ، اعتمد.
- الاعتماد على ROLLBACK بعد أوامر البنية: في بعض الأنظمة مثل MySQL، أوامر مثل
CREATE TABLEوDROP TABLEتُعتمد فوراً ولا يمكن التراجع عنها.
اختبر نفسك
Section titled “اختبر نفسك”سؤال
نفّذت BEGIN ثم أمرَي UPDATE ثم انقطع الاتصال قبل COMMIT. ماذا يحدث للتعديلين؟
المعاملة غير المعتمدة تُلغى كاملة. هذه هي خاصية الذرّية: الكل أو لا شيء.
سؤال
ما وظيفة ROLLBACK TO name بعد SAVEPOINT name؟
تعيد البيانات إلى حالتها عند نقطة الحفظ، وتبقى المعاملة مفتوحة بما قبلها.
الخلاصة
Section titled “الخلاصة”- المعاملة مجموعة أوامر تنجح كلها أو تُلغى كلها.
BEGINتبدأها، وCOMMITتعتمدها، وROLLBACKتتراجع عنها.- عند فشل أي أمر داخل المعاملة أرسل
ROLLBACK، وSAVEPOINTللتراجع الجزئي. - بدون
BEGINكل أمر يُعتمد تلقائياً ولا رجعة فيه. - مبروك! أنهيت كورس SQL: من أول
SELECTإلى الفهارس والمعاملات، وهي أدوات تعمل مع كل قواعد البيانات العلائقية.