الأمان والأداء
تحسين أداء قاعدة البيانات
تحسين أداء قاعدة البيانات
أداء قاعدة البيانات حاسم لقابلية تطبيقك للتوسع. الاستعلامات البطيئة يمكن أن تعطل النظام بأكمله تحت الضغط.
أساسيات تحسين الاستعلامات
اكتب دائمًا استعلامات فعالة تقلل من نقل البيانات والمعالجة:
<!-- سيء: SELECT * يسترجع بيانات غير ضرورية -->
SELECT * FROM users WHERE status = 'active';
<!-- جيد: حدد الأعمدة المطلوبة فقط -->
SELECT id, name, email FROM users WHERE status = 'active';
<!-- سيء: مشكلة استعلام N+1 -->
$users = User::all();
foreach ($users as $user) {
echo $user->profile->bio; // ينفذ استعلام واحد لكل مستخدم
}
<!-- جيد: التحميل المسبق -->
$users = User::with('profile')->get();
foreach ($users as $user) {
echo $user->profile->bio; // بدون استعلامات إضافية
}
SELECT * FROM users WHERE status = 'active';
<!-- جيد: حدد الأعمدة المطلوبة فقط -->
SELECT id, name, email FROM users WHERE status = 'active';
<!-- سيء: مشكلة استعلام N+1 -->
$users = User::all();
foreach ($users as $user) {
echo $user->profile->bio; // ينفذ استعلام واحد لكل مستخدم
}
<!-- جيد: التحميل المسبق -->
$users = User::with('profile')->get();
foreach ($users as $user) {
echo $user->profile->bio; // بدون استعلامات إضافية
}
استراتيجيات الفهرسة
الفهارس تسرع عمليات القراءة بشكل كبير لكنها تبطئ الكتابة. اختر الفهارس بعناية:
<!-- إنشاء فهرس عمود واحد -->
CREATE INDEX idx_users_email ON users(email);
<!-- إنشاء فهرس مركب لأعمدة متعددة -->
CREATE INDEX idx_orders_user_status ON orders(user_id, status);
<!-- فهرس فريد للقيود -->
CREATE UNIQUE INDEX idx_users_username ON users(username);
<!-- فهرس نص كامل للبحث -->
CREATE FULLTEXT INDEX idx_posts_content ON posts(title, content);
CREATE INDEX idx_users_email ON users(email);
<!-- إنشاء فهرس مركب لأعمدة متعددة -->
CREATE INDEX idx_orders_user_status ON orders(user_id, status);
<!-- فهرس فريد للقيود -->
CREATE UNIQUE INDEX idx_users_username ON users(username);
<!-- فهرس نص كامل للبحث -->
CREATE FULLTEXT INDEX idx_posts_content ON posts(title, content);
أفضل ممارسات الفهرسة: فهرس المفاتيح الأجنبية والأعمدة المبحوث عنها بشكل متكرر وعبارات WHERE/ORDER BY. لا تفرط في الفهرسة - كل فهرس يضيف عبئًا على عمليات INSERT/UPDATE.
تحليل EXPLAIN
استخدم EXPLAIN لفهم خطط تنفيذ الاستعلامات وتحديد الاختناقات:
<!-- تحليل أداء الاستعلام -->
EXPLAIN SELECT u.name, COUNT(o.id) as order_count
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE u.created_at > '2024-01-01'
GROUP BY u.id
ORDER BY order_count DESC
LIMIT 10;
<!-- الأعمدة الرئيسية في نتيجة EXPLAIN: -->
<!-- type: ALL (سيء), index, range, ref, eq_ref, const (جيد) -->
<!-- possible_keys: الفهارس التي يمكن استخدامها -->
<!-- key: الفهرس المستخدم فعليًا -->
<!-- rows: الصفوف المقدرة للفحص -->
<!-- Extra: Using filesort, Using temporary (كلاهما يشير لمشاكل أداء) -->
EXPLAIN SELECT u.name, COUNT(o.id) as order_count
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE u.created_at > '2024-01-01'
GROUP BY u.id
ORDER BY order_count DESC
LIMIT 10;
<!-- الأعمدة الرئيسية في نتيجة EXPLAIN: -->
<!-- type: ALL (سيء), index, range, ref, eq_ref, const (جيد) -->
<!-- possible_keys: الفهارس التي يمكن استخدامها -->
<!-- key: الفهرس المستخدم فعليًا -->
<!-- rows: الصفوف المقدرة للفحص -->
<!-- Extra: Using filesort, Using temporary (كلاهما يشير لمشاكل أداء) -->
تحذير: إذا رأيت "type: ALL" أو "Extra: Using filesort" في نتيجة EXPLAIN، فاستعلامك يحتاج على الأرجح للتحسين أو فهارس إضافية.
سجل الاستعلامات البطيئة
فعّل تسجيل الاستعلامات البطيئة لتحديد الاستعلامات الإشكالية في الإنتاج:
<!-- تكوين MySQL (my.cnf) -->
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow-query.log
long_query_time = 2
<!-- تحليل الاستعلامات البطيئة -->
mysqldumpslow -s t -t 10 /var/log/mysql/slow-query.log
<!-- تسجيل استعلامات Laravel -->
DB::enableQueryLog();
// ... تنفيذ الاستعلامات ...
$queries = DB::getQueryLog();
foreach ($queries as $query) {
if ($query['time'] > 100) { // ميلي ثانية
Log::warning('استعلام بطيء', $query);
}
}
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow-query.log
long_query_time = 2
<!-- تحليل الاستعلامات البطيئة -->
mysqldumpslow -s t -t 10 /var/log/mysql/slow-query.log
<!-- تسجيل استعلامات Laravel -->
DB::enableQueryLog();
// ... تنفيذ الاستعلامات ...
$queries = DB::getQueryLog();
foreach ($queries as $query) {
if ($query['time'] > 100) { // ميلي ثانية
Log::warning('استعلام بطيء', $query);
}
}
إلغاء التطبيع
أحيانًا كسر قواعد التطبيع يحسن الأداء:
<!-- مطبّع: يتطلب JOIN للحصول على عدد المنشورات -->
SELECT u.*, COUNT(p.id) as post_count
FROM users u
LEFT JOIN posts p ON u.id = p.user_id
GROUP BY u.id;
<!-- غير مطبّع: أضف عمود post_count لجدول users -->
ALTER TABLE users ADD COLUMN post_count INT DEFAULT 0;
<!-- التحديث بمحفز أو منطق التطبيق -->
CREATE TRIGGER increment_post_count AFTER INSERT ON posts
FOR EACH ROW
UPDATE users SET post_count = post_count + 1 WHERE id = NEW.user_id;
<!-- الآن الاستعلام بسيط وسريع -->
SELECT * FROM users ORDER BY post_count DESC;
SELECT u.*, COUNT(p.id) as post_count
FROM users u
LEFT JOIN posts p ON u.id = p.user_id
GROUP BY u.id;
<!-- غير مطبّع: أضف عمود post_count لجدول users -->
ALTER TABLE users ADD COLUMN post_count INT DEFAULT 0;
<!-- التحديث بمحفز أو منطق التطبيق -->
CREATE TRIGGER increment_post_count AFTER INSERT ON posts
FOR EACH ROW
UPDATE users SET post_count = post_count + 1 WHERE id = NEW.user_id;
<!-- الآن الاستعلام بسيط وسريع -->
SELECT * FROM users ORDER BY post_count DESC;
مقايضة: إلغاء التطبيع يضحي بسلامة البيانات من أجل الأداء. استخدمه للقيم المحسوبة التي يكلف حسابها كثيرًا لكنها نادرًا ما تتغير.
نسخ القراءة المتماثلة
قم بتوسيع عمليات القراءة أفقيًا بنسخ قاعدة البيانات:
<!-- تكوين Laravel database.php -->
'mysql' => [
'read' => [
'host' => [
'192.168.1.2', // نسخة 1
'192.168.1.3', // نسخة 2
],
],
'write' => [
'host' => [
'192.168.1.1', // الأساسية
],
],
'driver' => 'mysql',
// ... تكوينات أخرى
],
<!-- القراءات تذهب تلقائيًا للنسخ المتماثلة -->
$users = User::all(); // يستخدم نسخة القراءة
<!-- الكتابات تذهب للأساسية -->
User::create([...]); // يستخدم قاعدة البيانات الأساسية
'mysql' => [
'read' => [
'host' => [
'192.168.1.2', // نسخة 1
'192.168.1.3', // نسخة 2
],
],
'write' => [
'host' => [
'192.168.1.1', // الأساسية
],
],
'driver' => 'mysql',
// ... تكوينات أخرى
],
<!-- القراءات تذهب تلقائيًا للنسخ المتماثلة -->
$users = User::all(); // يستخدم نسخة القراءة
<!-- الكتابات تذهب للأساسية -->
User::create([...]); // يستخدم قاعدة البيانات الأساسية
تخزين الاستعلامات مؤقتًا
خزّن نتائج الاستعلامات المكلفة مؤقتًا لتقليل حمل قاعدة البيانات:
<!-- تخزين استعلامات Laravel مؤقتًا -->
use Illuminate\Support\Facades\Cache;
$users = Cache::remember('active_users', 3600, function () {
return User::where('status', 'active')
->with('profile', 'roles')
->get();
});
<!-- امسح الذاكرة المؤقتة عند تغيير البيانات -->
public function update(Request $request, User $user)
{
$user->update($request->validated());
Cache::forget('active_users');
return redirect()->back();
}
use Illuminate\Support\Facades\Cache;
$users = Cache::remember('active_users', 3600, function () {
return User::where('status', 'active')
->with('profile', 'roles')
->get();
});
<!-- امسح الذاكرة المؤقتة عند تغيير البيانات -->
public function update(Request $request, User $user)
{
$user->update($request->validated());
Cache::forget('active_users');
return redirect()->back();
}
تمرين: ابحث عن استعلام بطيء في تطبيقك. استخدم EXPLAIN لتحليله، أضف الفهارس المناسبة، وقِس تحسين الأداء. استهدف تقليل وقت التنفيذ بنسبة 50٪ على الأقل.