الهندسةMar 28, 2026
Deep EVM #23: تصحيح الأداء — عندما تقتل قراءات قاعدة البيانات زمن الاستجابة
OS
Open Soft Team
Engineering Team
عندما تكون قاعدة البيانات عنق الزجاجة
أكثر مشاكل الأداء شيوعاً في تطبيقات الويب ليست في الكود — بل في قاعدة البيانات. استعلام بطيء واحد يمكن أن يحول API سريع إلى كابوس.
مشكلة N+1
النمط الأكثر شيوعاً والأكثر ضرراً:
// سيء: N+1 استعلام
let articles = sqlx::query_as::<_, Article>("SELECT * FROM articles")
.fetch_all(&pool).await?;
for article in &articles {
// استعلام منفصل لكل مقالة!
let tags = sqlx::query_as::<_, Tag>(
"SELECT t.* FROM tags t JOIN article_tags at ON t.id = at.tag_id WHERE at.article_id = $1"
)
.bind(article.id)
.fetch_all(&pool).await?;
}
// جيد: استعلام واحد مع JOIN
let results = sqlx::query_as::<_, ArticleWithTags>(
"SELECT a.*, array_agg(t.name) as tags FROM articles a
LEFT JOIN article_tags at ON a.id = at.article_id
LEFT JOIN tags t ON t.id = at.tag_id
GROUP BY a.id"
)
.fetch_all(&pool).await?;
الفهارس المفقودة
-- استعلام بطيء بدون فهرس
EXPLAIN ANALYZE SELECT * FROM articles WHERE slug = 'my-article';
-- Seq Scan: 500ms
-- إضافة فهرس
CREATE INDEX idx_articles_slug ON articles(slug);
-- بعد الفهرس
EXPLAIN ANALYZE SELECT * FROM articles WHERE slug = 'my-article';
-- Index Scan: 0.5ms
تجميع الاتصالات
let pool = PgPoolOptions::new()
.max_connections(50)
.min_connections(5)
.acquire_timeout(Duration::from_secs(5))
.idle_timeout(Duration::from_secs(300))
.connect(&database_url)
.await?;
أدوات التشخيص
- EXPLAIN ANALYZE — خطة تنفيذ الاستعلام
- pg_stat_statements — إحصائيات الاستعلامات
- pgBadger — تحليل سجلات PostgreSQL
- tracing مع sqlx — تسجيل كل استعلام مع المدة
الخلاصة
تصحيح أداء قاعدة البيانات يبدأ بالقياس. EXPLAIN ANALYZE لكل استعلام بطيء، فهارس للأعمدة المستخدمة في WHERE وJOIN، وتجنب مشكلة N+1 باستخدام JOIN.