MySQL وتصميم قواعد البيانات

الأحداث والمهام المجدولة (Events & Scheduled Tasks)

13 دقيقة الدرس 23 من 40

الأحداث والمهام المجدولة (Events & Scheduled Tasks)

أحداث MySQL هي مهام مجدولة تعمل تلقائياً في أوقات أو فترات محددة. إنها مثالية لصيانة قاعدة البيانات وإنشاء التقارير وأرشفة البيانات وغيرها من العمليات المتكررة. فكر فيها كوظائف cron داخل MySQL نفسها.

ما هي أحداث MySQL؟

الأحداث هي كائنات قاعدة بيانات مُسماة تنفذ عبارات SQL في أوقات مجدولة. تعمل بشكل مستقل في الخلفية دون الحاجة إلى نصوص أو تطبيقات خارجية.

مفهوم أساسي: تتطلب الأحداث تمكين جدولة الأحداث (Event Scheduler). إنها مثالية لمهام صيانة قاعدة البيانات الآلية وتنظيف البيانات وإعداد التقارير المجدولة.

تمكين جدولة الأحداث

أولاً، تأكد من تشغيل جدولة الأحداث:

-- التحقق من تمكين جدولة الأحداث SHOW VARIABLES LIKE 'event_scheduler'; -- تمكين جدولة الأحداث (الجلسة الحالية) SET GLOBAL event_scheduler = ON; -- تعطيل جدولة الأحداث SET GLOBAL event_scheduler = OFF; -- التحقق من الأحداث قيد التشغيل SHOW PROCESSLIST;
نصيحة: لتمكين جدولة الأحداث بشكل دائم، أضف `event_scheduler=ON` إلى ملف التكوين my.cnf أو my.ini.

إنشاء أحداث لمرة واحدة

تُنفذ أحداث المرة الواحدة مرة واحدة في تاريخ ووقت محددين:

-- حدث بسيط لمرة واحدة CREATE EVENT cleanup_temp_data ON SCHEDULE AT '2024-12-31 23:59:59' DO DELETE FROM temp_sessions WHERE created_at < DATE_SUB(NOW(), INTERVAL 24 HOUR); -- حدث يُنفذ بعد ساعة واحدة من الآن CREATE EVENT send_daily_report ON SCHEDULE AT CURRENT_TIMESTAMP + INTERVAL 1 HOUR DO BEGIN INSERT INTO reports (report_type, generated_at, data) SELECT 'daily_sales', NOW(), JSON_OBJECT( 'total_orders', COUNT(*), 'total_revenue', SUM(total_amount) ) FROM orders WHERE DATE(order_date) = CURDATE(); END; -- حدث بتاريخ ووقت محددين CREATE EVENT year_end_summary ON SCHEDULE AT '2024-12-31 00:00:00' DO CALL GenerateAnnualReport();

إنشاء أحداث متكررة

تُنفذ الأحداث المتكررة بشكل متكرر على فترات محددة:

-- حدث يعمل كل يوم CREATE EVENT daily_cleanup ON SCHEDULE EVERY 1 DAY STARTS '2024-01-01 02:00:00' DO BEGIN -- حذف السجلات القديمة DELETE FROM activity_logs WHERE created_at < DATE_SUB(NOW(), INTERVAL 90 DAY); -- أرشفة الطلبات القديمة INSERT INTO archived_orders SELECT * FROM orders WHERE status = 'completed' AND completed_at < DATE_SUB(NOW(), INTERVAL 1 YEAR); DELETE FROM orders WHERE order_id IN ( SELECT order_id FROM archived_orders ); END; -- حدث يعمل كل ساعة CREATE EVENT hourly_stats_update ON SCHEDULE EVERY 1 HOUR STARTS CURRENT_TIMESTAMP DO UPDATE statistics SET active_users = (SELECT COUNT(*) FROM users WHERE last_active > DATE_SUB(NOW(), INTERVAL 1 HOUR)), hourly_revenue = (SELECT SUM(total_amount) FROM orders WHERE created_at > DATE_SUB(NOW(), INTERVAL 1 HOUR)), updated_at = NOW() WHERE stat_type = 'hourly'; -- حدث يعمل كل 30 دقيقة CREATE EVENT update_trending_products ON SCHEDULE EVERY 30 MINUTE DO CALL CalculateTrendingProducts();

وحدات الوقت للأحداث

-- الفترات الزمنية المتاحة EVERY 1 SECOND EVERY 5 MINUTE EVERY 1 HOUR EVERY 1 DAY EVERY 1 WEEK EVERY 1 MONTH EVERY 1 QUARTER EVERY 1 YEAR -- فترات زمنية مركبة EVERY '1:30' HOUR_MINUTE -- كل ساعة و 30 دقيقة EVERY '1 2' DAY_HOUR -- كل يوم و ساعتين EVERY '3 0:0:0' DAY_SECOND -- كل 3 أيام

أوقات البدء والانتهاء للأحداث

التحكم في وقت بدء وإيقاف تنفيذ الأحداث:

-- حدث مع وقت البدء CREATE EVENT promotional_emails ON SCHEDULE EVERY 1 DAY STARTS '2024-06-01 08:00:00' DO CALL SendPromotionalEmails(); -- حدث مع أوقات البدء والانتهاء CREATE EVENT summer_sale_reminder ON SCHEDULE EVERY 1 DAY STARTS '2024-06-01 09:00:00' ENDS '2024-08-31 23:59:59' DO BEGIN UPDATE promotions SET emails_sent = emails_sent + 1 WHERE promo_code = 'SUMMER2024'; -- منطق إرسال التذكير هنا END; -- حدث يعمل لمدة 30 يوماً ثم يتوقف CREATE EVENT trial_period_check ON SCHEDULE EVERY 1 DAY STARTS CURRENT_TIMESTAMP ENDS CURRENT_TIMESTAMP + INTERVAL 30 DAY DO CALL CheckTrialExpirations();

الحفاظ على الأحداث واكتمالها

-- حذف الحدث بعد الاكتمال (افتراضي لأحداث المرة الواحدة) CREATE EVENT temp_cleanup ON SCHEDULE AT CURRENT_TIMESTAMP + INTERVAL 1 HOUR ON COMPLETION NOT PRESERVE DO DELETE FROM temp_data WHERE created_at < DATE_SUB(NOW(), INTERVAL 1 DAY); -- الاحتفاظ بالحدث بعد الاكتمال CREATE EVENT important_task ON SCHEDULE AT '2024-12-31 23:59:59' ON COMPLETION PRESERVE DO CALL CriticalYearEndTask(); -- الحفاظ على الحدث المتكرر حتى بعد وقت ENDS CREATE EVENT seasonal_task ON SCHEDULE EVERY 1 DAY STARTS '2024-01-01 00:00:00' ENDS '2024-12-31 23:59:59' ON COMPLETION PRESERVE DO CALL SeasonalMaintenance();

إدارة حالة الأحداث

-- إنشاء حدث معطل (لن يعمل حتى يتم تمكينه) CREATE EVENT backup_database ON SCHEDULE EVERY 1 DAY STARTS '2024-01-01 03:00:00' DISABLE DO CALL BackupAllTables(); -- تمكين/تعطيل الأحداث ALTER EVENT backup_database ENABLE; ALTER EVENT backup_database DISABLE; -- تعطيل حدث مؤقتاً ALTER EVENT daily_cleanup DISABLE ON SLAVE;

تعديل الأحداث

-- تغيير جدول الحدث ALTER EVENT daily_cleanup ON SCHEDULE EVERY 2 DAY; -- تغيير إجراء الحدث ALTER EVENT daily_cleanup DO BEGIN DELETE FROM logs WHERE created_at < DATE_SUB(NOW(), INTERVAL 180 DAY); OPTIMIZE TABLE logs; END; -- إعادة تسمية الحدث ALTER EVENT old_event_name RENAME TO new_event_name; -- تغيير خصائص متعددة ALTER EVENT update_statistics ON SCHEDULE EVERY 15 MINUTE STARTS CURRENT_TIMESTAMP ENABLE COMMENT 'تم التحديث للعمل بشكل أكثر تكراراً';

أمثلة واقعية للأحداث

-- صيانة قاعدة البيانات الآلية CREATE EVENT weekly_maintenance ON SCHEDULE EVERY 1 WEEK STARTS '2024-01-07 03:00:00' -- الأحد في الساعة 3 صباحاً DO BEGIN -- تحسين الجداول OPTIMIZE TABLE orders, order_items, customers, products; -- تحليل الجداول لتحسين الاستعلام ANALYZE TABLE orders, customers; -- تنظيف الملفات المؤقتة القديمة DELETE FROM temp_uploads WHERE created_at < DATE_SUB(NOW(), INTERVAL 7 DAY); -- تسجيل الصيانة INSERT INTO maintenance_log (task, completed_at) VALUES ('weekly_maintenance', NOW()); END; -- تنظيف الجلسات CREATE EVENT cleanup_expired_sessions ON SCHEDULE EVERY 30 MINUTE DO BEGIN DELETE FROM sessions WHERE last_activity < DATE_SUB(NOW(), INTERVAL 2 HOUR); DELETE FROM password_resets WHERE created_at < DATE_SUB(NOW(), INTERVAL 1 HOUR); END; -- تتبع تفاعل العملاء CREATE EVENT calculate_customer_metrics ON SCHEDULE EVERY 1 DAY STARTS '2024-01-01 01:00:00' DO BEGIN -- تحديث القيمة الدائمة للعميل UPDATE customers c SET lifetime_value = ( SELECT COALESCE(SUM(total_amount), 0) FROM orders WHERE customer_id = c.customer_id ), last_purchase_date = ( SELECT MAX(order_date) FROM orders WHERE customer_id = c.customer_id ); -- حساب خطر التراجع UPDATE customers SET churn_risk = CASE WHEN DATEDIFF(NOW(), last_purchase_date) > 180 THEN 'high' WHEN DATEDIFF(NOW(), last_purchase_date) > 90 THEN 'medium' ELSE 'low' END WHERE last_purchase_date IS NOT NULL; END; -- أرشفة البيانات CREATE EVENT archive_old_data ON SCHEDULE EVERY 1 MONTH STARTS '2024-01-01 00:00:00' DO BEGIN DECLARE archived_count INT DEFAULT 0; -- أرشفة الطلبات المكتملة الأقدم من عامين INSERT INTO archived_orders SELECT * FROM orders WHERE status = 'completed' AND completed_at < DATE_SUB(NOW(), INTERVAL 2 YEAR); SET archived_count = ROW_COUNT(); -- حذف الطلبات المؤرشفة من الجدول الرئيسي DELETE FROM orders WHERE status = 'completed' AND completed_at < DATE_SUB(NOW(), INTERVAL 2 YEAR); -- تسجيل الأرشفة INSERT INTO archive_log (table_name, records_archived, archived_at) VALUES ('orders', archived_count, NOW()); END; -- إنشاء التقارير CREATE EVENT generate_monthly_reports ON SCHEDULE EVERY 1 MONTH STARTS '2024-01-01 06:00:00' DO BEGIN -- تقرير المبيعات INSERT INTO monthly_reports (report_type, period, data, generated_at) SELECT 'sales', DATE_FORMAT(DATE_SUB(NOW(), INTERVAL 1 MONTH), '%Y-%m'), JSON_OBJECT( 'total_orders', COUNT(*), 'total_revenue', SUM(total_amount), 'avg_order_value', AVG(total_amount), 'top_products', ( SELECT JSON_ARRAYAGG( JSON_OBJECT('product_id', product_id, 'quantity', SUM(quantity)) ) FROM order_items WHERE order_id IN ( SELECT order_id FROM orders WHERE MONTH(order_date) = MONTH(DATE_SUB(NOW(), INTERVAL 1 MONTH)) ) GROUP BY product_id ORDER BY SUM(quantity) DESC LIMIT 10 ) ), NOW() FROM orders WHERE MONTH(order_date) = MONTH(DATE_SUB(NOW(), INTERVAL 1 MONTH)) AND YEAR(order_date) = YEAR(DATE_SUB(NOW(), INTERVAL 1 MONTH)); END;

إدارة الأحداث

-- سرد جميع الأحداث في قاعدة البيانات الحالية SHOW EVENTS; -- إظهار الأحداث مع المرشحات SHOW EVENTS WHERE Name LIKE '%cleanup%'; -- عرض تعريف الحدث SHOW CREATE EVENT daily_cleanup; -- حذف حدث DROP EVENT IF EXISTS daily_cleanup; -- الاستعلام عن معلومات الحدث من information_schema SELECT EVENT_NAME, EVENT_DEFINITION, INTERVAL_VALUE, INTERVAL_FIELD, STATUS, STARTS, ENDS, LAST_EXECUTED FROM information_schema.EVENTS WHERE EVENT_SCHEMA = 'your_database' ORDER BY EVENT_NAME;

معالجة أخطاء الأحداث

DELIMITER // CREATE EVENT safe_cleanup ON SCHEDULE EVERY 1 DAY DO BEGIN DECLARE CONTINUE HANDLER FOR SQLEXCEPTION BEGIN -- تسجيل الخطأ INSERT INTO event_errors (event_name, error_message, occurred_at) VALUES ('safe_cleanup', 'حدث خطأ أثناء التنظيف', NOW()); END; -- عمليات التنظيف DELETE FROM temp_data WHERE created_at < DATE_SUB(NOW(), INTERVAL 7 DAY); DELETE FROM old_logs WHERE created_at < DATE_SUB(NOW(), INTERVAL 30 DAY); -- تسجيل النجاح INSERT INTO event_log (event_name, status, executed_at) VALUES ('safe_cleanup', 'success', NOW()); END // DELIMITER ;

مراقبة الأحداث

-- إنشاء سجل تنفيذ الأحداث CREATE TABLE event_execution_log ( log_id INT AUTO_INCREMENT PRIMARY KEY, event_name VARCHAR(64), execution_time TIMESTAMP, duration_seconds INT, rows_affected INT, status VARCHAR(20), error_message TEXT ); -- حدث مع التسجيل CREATE EVENT monitored_cleanup ON SCHEDULE EVERY 1 HOUR DO BEGIN DECLARE start_time TIMESTAMP DEFAULT NOW(); DECLARE affected_rows INT DEFAULT 0; -- تنفيذ التنظيف DELETE FROM temp_sessions WHERE created_at < DATE_SUB(NOW(), INTERVAL 2 HOUR); SET affected_rows = ROW_COUNT(); -- تسجيل التنفيذ INSERT INTO event_execution_log ( event_name, execution_time, duration_seconds, rows_affected, status ) VALUES ( 'monitored_cleanup', start_time, TIMESTAMPDIFF(SECOND, start_time, NOW()), affected_rows, 'completed' ); END;

تمرين عملي:

إنشاء نظام صيانة مجدول كامل:

  1. أنشئ حدثاً 'daily_analytics' يعمل كل يوم في الساعة 2 صباحاً لـ:
    • حساب إجماليات المبيعات اليومية
    • تحديث تصنيفات شعبية المنتجات
    • تحديد العملاء غير النشطين (لا توجد طلبات في 60 يوماً)
    • إنشاء تقرير ملخص في جدول التقارير
  2. أنشئ حدثاً 'hourly_cache_refresh' يعمل كل ساعة لتحديث الإحصائيات المخزنة
  3. أنشئ حدثاً لمرة واحدة يعمل في نهاية الشهر لأرشفة بيانات الشهر الماضي
  4. أضف معالجة أخطاء وتسجيل مناسب لجميع الأحداث

أفضل الممارسات

✓ قم دائماً بتمكين جدولة الأحداث في الإنتاج ✓ جدولة الصيانة خلال فترات منخفضة الحركة ✓ استخدم المعاملات لتعديلات البيانات ✓ أضف معالجة الأخطاء للأحداث ✓ سجل تنفيذات الأحداث للمراقبة ✓ اختبر الأحداث بدقة قبل النشر ✓ استخدم PRESERVE للأحداث المتكررة المهمة ✓ وثق أغراض وجداول الأحداث ✓ راقب أوقات تنفيذ الأحداث ✗ لا تشغل عمليات ثقيلة خلال ساعات الذروة ✗ تجنب الأحداث التي تقفل الجداول لفترات طويلة ✗ لا تنشئ الكثير من الأحداث المتزامنة

الملخص

في هذا الدرس، تعلمت:

  • الأحداث هي مهام مجدولة تعمل تلقائياً في MySQL
  • أحداث المرة الواحدة تُنفذ مرة واحدة في تاريخ ووقت محددين
  • الأحداث المتكررة تُنفذ بشكل متكرر على فترات محددة
  • يجب تمكين جدولة الأحداث لتشغيل الأحداث
  • يمكن تمكين الأحداث أو تعطيلها أو تعديلها أو حذفها
  • STARTS و ENDS تتحكم في نوافذ تنفيذ الأحداث
  • الأحداث مثالية للصيانة والتنظيف وإعداد التقارير
  • أضف دائماً معالجة الأخطاء والتسجيل للأحداث
التالي: في الدرس التالي، سنستكشف المؤشرات وأنماط الإجراءات المتقدمة لمعالجة البيانات المعقدة!