مستويات العزل والتزامن
عندما تعمل معاملات متعددة في وقت واحد في قاعدة البيانات، يمكن أن تتداخل مع بعضها البعض بطرق غير متوقعة. تحدد مستويات عزل المعاملات كيفية ظهور التغييرات التي أجرتها معاملة واحدة للمعاملات المتزامنة الأخرى، مع الموازنة بين اتساق البيانات وأداء النظام.
تحدي العزل
تخيل مستخدمين يحاولان حجز آخر مقعد متاح على رحلة طيران في نفس الوقت. بدون عزل مناسب، قد يرى كلاهما المقعد متاحاً وينجحان في حجزه، مما يؤدي إلى فساد البيانات. تمنع مستويات العزل مثل هذه التضاربات.
المقايضة: توفر مستويات العزل الأعلى اتساقاً أكبر ولكنها تقلل من التزامن والأداء. المستويات الأقل تحسن الأداء ولكن يمكن أن تؤدي إلى شذوذات. يعتمد اختيار المستوى المناسب على احتياجات تطبيقك.
مستويات العزل الأربعة
يحدد SQL أربعة مستويات عزل قياسية، من الأقل إلى الأكثر عزلاً:
1. READ UNCOMMITTED - أقل عزل، أعلى أداء
2. READ COMMITTED - الافتراضي في العديد من قواعد البيانات
3. REPEATABLE READ - الافتراضي في MySQL/InnoDB
4. SERIALIZABLE - أعلى عزل، أقل أداء
تعيين مستويات العزل
يمكنك تعيين مستويات العزل على مستوى الجلسة أو المعاملة:
-- تحقق من مستوى العزل الحالي
SELECT @@transaction_isolation;
-- تعيين للجلسة الحالية
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
-- تعيين للمعاملة التالية فقط
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
-- تعيين عالمياً (يتطلب صلاحيات)
SET GLOBAL TRANSACTION ISOLATION LEVEL REPEATABLE READ;
1. READ UNCOMMITTED
أدنى مستوى عزل يسمح للمعاملات بقراءة التغييرات غير الملتزم بها من معاملات أخرى. هذا يمكن أن يؤدي إلى "قراءات قذرة".
-- المعاملة 1
START TRANSACTION;
UPDATE products SET price = 99.99 WHERE product_id = 1;
-- لم يتم الالتزام بعد
-- المعاملة 2 (في جلسة أخرى، READ UNCOMMITTED)
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
START TRANSACTION;
SELECT price FROM products WHERE product_id = 1;
-- يرى 99.99 على الرغم من أن المعاملة 1 لم تلتزم!
-- إذا تراجعت المعاملة 1، فإن المعاملة 2 قرأت بيانات غير صحيحة
مشكلة القراءة القذرة: تقرأ المعاملة 2 بيانات قد تتراجع عنها المعاملة 1. نادراً ما يُستخدم هذا المستوى من العزل ويناسب فقط التحليلات أو السجلات حيث البيانات التقريبية مقبولة.
2. READ COMMITTED
يمنع هذا المستوى القراءات القذرة من خلال السماح للمعاملات فقط بقراءة البيانات الملتزم بها. ومع ذلك، يسمح بـ "القراءات غير المتكررة".
-- المعاملة 1
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
START TRANSACTION;
SELECT price FROM products WHERE product_id = 1;
-- يرجع 50.00
-- في هذه الأثناء، المعاملة 2 تلتزم بتغيير
-- (في جلسة أخرى)
UPDATE products SET price = 75.00 WHERE product_id = 1;
COMMIT;
-- العودة إلى المعاملة 1
SELECT price FROM products WHERE product_id = 1;
-- الآن يرجع 75.00 - مختلف عن قبل!
COMMIT;
القراءة غير المتكررة: نفس الاستعلام يعيد نتائج مختلفة داخل نفس المعاملة لأن معاملة أخرى عدلت البيانات والتزمت بها. هذا مقبول للعديد من التطبيقات.
3. REPEATABLE READ (افتراضي MySQL)
يمنع هذا المستوى القراءات القذرة والقراءات غير المتكررة من خلال ضمان أن البيانات المقروءة مرة واحدة تظل متسقة طوال المعاملة:
-- المعاملة 1
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
START TRANSACTION;
SELECT price FROM products WHERE product_id = 1;
-- يرجع 50.00
-- المعاملة 2 تغير وتلتزم
-- (في جلسة أخرى)
UPDATE products SET price = 75.00 WHERE product_id = 1;
COMMIT;
-- العودة إلى المعاملة 1
SELECT price FROM products WHERE product_id = 1;
-- لا يزال يرجع 50.00 (قراءة متكررة!)
COMMIT;
-- بعد الالتزام، المعاملات الجديدة ترى 75.00
REPEATABLE READ لا يزال يمكن أن يشهد "قراءات وهمية" نظرياً، على الرغم من أن InnoDB يمنعها:
-- المعاملة 1
START TRANSACTION;
SELECT COUNT(*) FROM orders WHERE customer_id = 123;
-- يرجع 5
-- المعاملة 2 تدرج طلباً جديداً
-- (في جلسة أخرى)
INSERT INTO orders (customer_id, order_date) VALUES (123, NOW());
COMMIT;
-- المعاملة 1
SELECT COUNT(*) FROM orders WHERE customer_id = 123;
-- في REPEATABLE READ النقي: قد يرجع 6 (قراءة وهمية)
-- في InnoDB: لا يزال يرجع 5 (منع القراءات الوهمية)
ميزة MySQL: يستخدم InnoDB "قفل المفتاح التالي" الذي يمنع القراءات الوهمية حتى عند مستوى REPEATABLE READ، مما يمنحك اتساقاً شبيهاً بـ SERIALIZABLE مع أداء أفضل.
4. SERIALIZABLE
أعلى مستوى عزل يجعل المعاملات تنفذ كما لو كانت تعمل بشكل تسلسلي (واحدة تلو الأخرى):
-- المعاملة 1
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
START TRANSACTION;
SELECT * FROM products WHERE category = 'Electronics';
-- هذا يضع أقفالاً على جميع الصفوف والفجوات المطابقة
-- المعاملة 2
START TRANSACTION;
INSERT INTO products (name, category, price)
VALUES ('New Phone', 'Electronics', 599.99);
-- هذا ينتظر! المعاملة 1 قفلت الفئة
-- يجب أن تلتزم المعاملة 1 أولاً
COMMIT;
-- الآن يمكن للمعاملة 2 المتابعة
COMMIT;
تأثير الأداء: SERIALIZABLE يقلل بشكل كبير من التزامن ويمكن أن يتسبب في انتظار العديد من المعاملات. استخدمه فقط عندما يكون الاتساق المطلق مطلوباً (مثل التسوية المالية).
فهم ظواهر القراءة
إليك ملخص لما يمنعه كل مستوى عزل:
مستوى العزل | قراءة قذرة | قراءة غير متكررة | قراءة وهمية
-------------------|-----------|------------------|------------
READ UNCOMMITTED | ممكنة | ممكنة | ممكنة
READ COMMITTED | ممنوعة | ممكنة | ممكنة
REPEATABLE READ | ممنوعة | ممنوعة | ممنوعة*
SERIALIZABLE | ممنوعة | ممنوعة | ممنوعة
* InnoDB يمنع القراءات الوهمية عند REPEATABLE READ
مثال من الواقع: حساب مصرفي
فكر في التحقق من رصيد الحساب أثناء معالجة التحويلات:
-- السيناريو: المستخدم يتحقق من الرصيد أثناء معالجة التحويل
-- معاملة التحويل (الجلسة 1)
START TRANSACTION;
UPDATE accounts SET balance = balance - 1000 WHERE account_id = 101;
-- المعالجة... تستغرق 5 ثوانٍ
UPDATE accounts SET balance = balance + 1000 WHERE account_id = 202;
COMMIT;
-- المستخدم يتحقق من الرصيد (الجلسة 2)
-- مع READ UNCOMMITTED:
SELECT SUM(balance) FROM accounts WHERE user_id = 5;
-- يظهر إجمالياً غير صحيح أثناء التحويل!
-- مع READ COMMITTED أو أعلى:
SELECT SUM(balance) FROM accounts WHERE user_id = 5;
-- ينتظر اكتمال التحويل أو يظهر حالة متسقة قبل التحويل
اختيار مستوى العزل المناسب
READ UNCOMMITTED:
✓ التحليلات، السجلات، العدّات التقريبية
✗ البيانات المالية، العمليات الحرجة
READ COMMITTED:
✓ تطبيقات الويب، عمليات CRUD العامة
✓ توازن جيد بين الاتساق والأداء
✗ العمليات التي تتطلب لقطات متسقة
REPEATABLE READ:
✓ افتراضي MySQL - ممتاز لمعظم التطبيقات
✓ التقارير التي تحتاج إلى عروض بيانات متسقة
✓ إدارة المخزون، أنظمة الحجز
SERIALIZABLE:
✓ التسوية المالية
✓ عمليات الامتثال الحرجة
✗ تطبيقات عالية الحركة (أداء ضعيف)
مثال عملي: نظام حجز المقاعد
إليك كيف تؤثر مستويات العزل المختلفة على نظام الحجز:
-- حجز مقعد مع REPEATABLE READ (موصى به)
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
START TRANSACTION;
-- تحقق إذا كان المقعد متاحاً
SELECT is_available INTO @available
FROM seats
WHERE seat_id = 42
FOR UPDATE; -- قفل الصف
IF @available = 1 THEN
-- احجز المقعد
UPDATE seats SET is_available = 0, customer_id = 123 WHERE seat_id = 42;
INSERT INTO bookings (seat_id, customer_id, booking_date)
VALUES (42, 123, NOW());
COMMIT;
SELECT 'الحجز ناجح' AS message;
ELSE
ROLLBACK;
SELECT 'المقعد غير متاح' AS message;
END IF;
نمط رئيسي: يقفل بند FOR UPDATE الصفوف بشكل صريح، مما يمنع المعاملات الأخرى من تعديلها. هذا أمر بالغ الأهمية لأنظمة الحجز وإدارة المخزون وأي عملية "تحقق ثم تحديث".
القراءات مع القفل
يمكنك التحكم بشكل صريح في سلوك القفل داخل المعاملات:
-- قفل مشترك (يمكن للمعاملات الأخرى القراءة ولكن ليس التعديل)
SELECT * FROM products WHERE product_id = 1 LOCK IN SHARE MODE;
-- قفل حصري (لا يمكن للمعاملات الأخرى القراءة أو التعديل)
SELECT * FROM products WHERE product_id = 1 FOR UPDATE;
-- صيغة MySQL 8.0+
SELECT * FROM products WHERE product_id = 1 FOR SHARE;
SELECT * FROM products WHERE product_id = 1 FOR UPDATE;
تمرين عملي:
السيناريو: نفذ نظام حجز تذاكر يمنع الحجز المزدوج.
المتطلبات:
- تحقق من توفر التذكرة
- اقفل التذكرة أثناء الفحص
- حدّث حالة التذكرة إلى مباعة
- سجل عملية الشراء
- استخدم مستوى العزل المناسب
الحل:
-- استخدم REPEATABLE READ (افتراضي MySQL جيد)
START TRANSACTION;
-- تحقق واقفل التذكرة
SELECT status, price INTO @status, @price
FROM tickets
WHERE ticket_id = 7890
FOR UPDATE; -- قفل حصري يمنع المعاملات الأخرى
-- تحقق من التوفر
IF @status = 'available' THEN
-- حدّث التذكرة
UPDATE tickets
SET status = 'sold',
sold_at = NOW(),
customer_id = 456
WHERE ticket_id = 7890;
-- سجل الشراء
INSERT INTO purchases (customer_id, ticket_id, amount, purchase_date)
VALUES (456, 7890, @price, NOW());
COMMIT;
SELECT 'تم شراء التذكرة بنجاح' AS message;
ELSE
ROLLBACK;
SELECT 'التذكرة لم تعد متاحة' AS message;
END IF;
الملخص
في هذا الدرس، تعلمت:
- تتحكم مستويات العزل في كيفية تفاعل المعاملات المتزامنة
- أربعة مستويات: READ UNCOMMITTED، READ COMMITTED، REPEATABLE READ، SERIALIZABLE
- عزل أعلى = اتساق أكبر ولكن أداء أقل
- REPEATABLE READ (الافتراضي) في MySQL ممتاز لمعظم حالات الاستخدام
- استخدم FOR UPDATE لقفل الصفوف بشكل صريح أثناء عمليات التحقق ثم التحديث
- اختر مستويات العزل بناءً على متطلبات اتساق تطبيقك
التالي: في الدرس التالي، سنستكشف الأقفال والجمود بالتفصيل، بما في ذلك كيفية اكتشافها ومنعها!