انتقل إلى المحتوى الرئيسي
الهندسة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.