MySQL وتصميم قواعد البيانات
الأحداث والمهام المجدولة (Events & Scheduled Tasks)
الأحداث والمهام المجدولة (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;
تمرين عملي:
إنشاء نظام صيانة مجدول كامل:
- أنشئ حدثاً 'daily_analytics' يعمل كل يوم في الساعة 2 صباحاً لـ:
- حساب إجماليات المبيعات اليومية
- تحديث تصنيفات شعبية المنتجات
- تحديد العملاء غير النشطين (لا توجد طلبات في 60 يوماً)
- إنشاء تقرير ملخص في جدول التقارير
- أنشئ حدثاً 'hourly_cache_refresh' يعمل كل ساعة لتحديث الإحصائيات المخزنة
- أنشئ حدثاً لمرة واحدة يعمل في نهاية الشهر لأرشفة بيانات الشهر الماضي
- أضف معالجة أخطاء وتسجيل مناسب لجميع الأحداث
أفضل الممارسات
✓ قم دائماً بتمكين جدولة الأحداث في الإنتاج
✓ جدولة الصيانة خلال فترات منخفضة الحركة
✓ استخدم المعاملات لتعديلات البيانات
✓ أضف معالجة الأخطاء للأحداث
✓ سجل تنفيذات الأحداث للمراقبة
✓ اختبر الأحداث بدقة قبل النشر
✓ استخدم PRESERVE للأحداث المتكررة المهمة
✓ وثق أغراض وجداول الأحداث
✓ راقب أوقات تنفيذ الأحداث
✗ لا تشغل عمليات ثقيلة خلال ساعات الذروة
✗ تجنب الأحداث التي تقفل الجداول لفترات طويلة
✗ لا تنشئ الكثير من الأحداث المتزامنة
الملخص
في هذا الدرس، تعلمت:
- الأحداث هي مهام مجدولة تعمل تلقائياً في MySQL
- أحداث المرة الواحدة تُنفذ مرة واحدة في تاريخ ووقت محددين
- الأحداث المتكررة تُنفذ بشكل متكرر على فترات محددة
- يجب تمكين جدولة الأحداث لتشغيل الأحداث
- يمكن تمكين الأحداث أو تعطيلها أو تعديلها أو حذفها
- STARTS و ENDS تتحكم في نوافذ تنفيذ الأحداث
- الأحداث مثالية للصيانة والتنظيف وإعداد التقارير
- أضف دائماً معالجة الأخطاء والتسجيل للأحداث
التالي: في الدرس التالي، سنستكشف المؤشرات وأنماط الإجراءات المتقدمة لمعالجة البيانات المعقدة!