استراتيجيات الفهرسة
تصميم الفهرس مهارة هندسة بيانات حرجة. أسئلة المقابلات غالباً تطلب منك تصميم فهارس لأنماط استعلام محددة أو استكشاف أخطاء الاستعلامات البطيئة.
أنواع الفهارس وحالات الاستخدام
فهارس B-Tree (الافتراضي)
نوع الفهرس الأكثر شيوعاً، مثالي لاستعلامات المساواة والنطاق.
-- إنشاء فهرس B-Tree
CREATE INDEX idx_orders_customer ON orders(customer_id);
-- جيد لـ:
SELECT * FROM orders WHERE customer_id = 123;
SELECT * FROM orders WHERE customer_id > 100 AND customer_id < 200;
SELECT * FROM orders WHERE customer_id IN (1, 2, 3);
| العملية | أداء B-Tree |
|---|---|
= المساواة | ممتاز |
<, >, BETWEEN | ممتاز |
LIKE 'prefix%' | جيد |
LIKE '%suffix' | ضعيف (مسح كامل) |
IS NULL | جيد |
فهارس Hash
محسنة لعمليات البحث عن المساواة الدقيقة فقط.
-- فهرس hash في PostgreSQL
CREATE INDEX idx_orders_status_hash ON orders USING HASH (status);
-- فعال فقط لـ:
SELECT * FROM orders WHERE status = 'pending';
-- غير مفيد لـ:
SELECT * FROM orders WHERE status != 'pending'; -- مسح كامل
SELECT * FROM orders WHERE status IN ('a', 'b'); -- عمليات بحث متعددة
الفهارس المركبة (متعددة الأعمدة)
حرجة للاستعلامات التي تفلتر على أعمدة متعددة.
-- فهرس مركب
CREATE INDEX idx_orders_customer_date
ON orders(customer_id, order_date);
قاعدة البادئة الأيسر: الفهرس يُستخدم عندما تفلتر الاستعلامات على الأعمدة الأيسر.
| الاستعلام | يستخدم الفهرس؟ |
|---|---|
WHERE customer_id = 1 | ✅ نعم |
WHERE customer_id = 1 AND order_date = '2024-01-01' | ✅ نعم |
WHERE order_date = '2024-01-01' | ❌ لا |
WHERE order_date = '2024-01-01' AND customer_id = 1 | ✅ نعم (المحسن يعيد الترتيب) |
سؤال المقابلة: "صمم فهارس لأنماط الاستعلام هذه"
-- استعلام 1: فلترة بالعميل، ترتيب بالتاريخ
SELECT * FROM orders WHERE customer_id = ? ORDER BY order_date DESC;
-- استعلام 2: فلترة بنطاق التاريخ والحالة
SELECT * FROM orders WHERE order_date BETWEEN ? AND ? AND status = ?;
-- استعلام 3: الحصول على آخر طلب لكل عميل
SELECT * FROM orders WHERE customer_id = ? ORDER BY order_date DESC LIMIT 1;
الجواب:
-- للاستعلام 1 و 3: customer_id أولاً، ثم order_date للترتيب
CREATE INDEX idx_customer_date ON orders(customer_id, order_date DESC);
-- للاستعلام 2: اعتبر الانتقائية - إذا كانت الحالة أكثر انتقائية
CREATE INDEX idx_status_date ON orders(status, order_date);
-- أو إذا كان نطاق التاريخ أكثر انتقائية
CREATE INDEX idx_date_status ON orders(order_date, status);
الفهارس الشاملة
تتضمن كل الأعمدة المطلوبة للاستعلام لتجنب البحث في الجدول.
-- الاستعلام
SELECT customer_id, order_date, total
FROM orders
WHERE customer_id = 123;
-- فهرس شامل
CREATE INDEX idx_covering ON orders(customer_id)
INCLUDE (order_date, total);
-- الآن الاستعلام يقرأ من الفهرس فقط، لا وصول للجدول
الفهارس الجزئية (المفلترة)
فهرس فقط الصفوف التي تطابق شرط—أصغر وأسرع.
-- فهرس فقط الطلبات المعلقة (مجموعة فرعية صغيرة)
CREATE INDEX idx_pending_orders ON orders(customer_id)
WHERE status = 'pending';
-- فعال لـ:
SELECT * FROM orders WHERE status = 'pending' AND customer_id = 123;
مبادئ تصميم الفهرس
1. تحليل الاستعلام أولاً
قبل إنشاء الفهارس، حلل أنماط الاستعلام الفعلية:
-- PostgreSQL: إيجاد الاستعلامات البطيئة
SELECT query, calls, mean_time, total_time
FROM pg_stat_statements
ORDER BY mean_time DESC
LIMIT 10;
-- حدد الأعمدة في:
-- 1. عبارات WHERE
-- 2. شروط JOIN
-- 3. عبارات ORDER BY
-- 4. عبارات GROUP BY
2. الانتقائية مهمة
الانتقائية العالية (القيم الفريدة) تجعل الفهارس أكثر فعالية.
-- تحقق من الانتقائية
SELECT
COUNT(DISTINCT status) as status_cardinality, -- منخفضة (مثلاً 5)
COUNT(DISTINCT customer_id) as customer_cardinality, -- عالية (مثلاً 100K)
COUNT(*) as total_rows
FROM orders;
-- فهرس customer_id أولاً (انتقائية عالية)
-- الحالة قد لا تحتاج فهرس وحدها
3. ترتيب الأعمدة في الفهارس المركبة
رتب حسب:
- شروط المساواة أولاً (تطابق دقيق)
- شروط النطاق أخيراً (>, <, BETWEEN)
- اعتبر ترتيب الفرز لـ ORDER BY
-- استعلام: WHERE status = 'active' AND created_at > '2024-01-01' ORDER BY created_at
-- أفضل فهرس:
CREATE INDEX idx_optimized ON table(status, created_at);
-- status (مساواة) → created_at (نطاق + ترتيب)
4. الوعي بتكلفة الفهرس
| الاعتبار | التأثير |
|---|---|
| أداء الكتابة | كل فهرس يبطئ INSERT/UPDATE/DELETE |
| التخزين | الفهارس تستهلك مساحة القرص |
| الصيانة | تجزئة الفهرس بمرور الوقت |
| Vacuum/Analyze | فهارس أكثر = صيانة أطول |
قاعدة عامة: استهدف 3-5 فهارس لكل جدول كحد أقصى. كل فهرس إضافي يحتاج تبرير قوي.
سيناريوهات المقابلة الشائعة
السيناريو 1: استعلام لوحة قيادة بطيء
المشكلة: "استعلام لوحة القيادة هذا يأخذ 30 ثانية"
SELECT
DATE(created_at) as date,
COUNT(*) as orders,
SUM(total) as revenue
FROM orders
WHERE created_at >= CURRENT_DATE - INTERVAL '30 days'
AND status = 'completed'
GROUP BY DATE(created_at);
الحل:
-- 1. إنشاء فهرس مركب
CREATE INDEX idx_orders_status_created ON orders(status, created_at)
WHERE status = 'completed'; -- فهرس جزئي إذا مدعوم
-- 2. اعتبر materialized view للوحة القيادة
CREATE MATERIALIZED VIEW daily_completed_orders AS
SELECT
DATE(created_at) as date,
COUNT(*) as orders,
SUM(total) as revenue
FROM orders
WHERE status = 'completed'
GROUP BY DATE(created_at);
السيناريو 2: أداء الربط
المشكلة: "الربط بين الطلبات والعملاء بطيء"
SELECT c.name, o.total
FROM orders o
JOIN customers c ON o.customer_id = c.id
WHERE c.country = 'USA';
الحل:
-- تأكد أن المفتاح الخارجي لديه فهرس
CREATE INDEX idx_orders_customer ON orders(customer_id);
-- فهرس لشرط الفلترة
CREATE INDEX idx_customers_country ON customers(country);
-- أو فهرس شامل
CREATE INDEX idx_customers_country_covering ON customers(country)
INCLUDE (id, name);
السيناريو 3: كشف الفهرس غير المستخدم
سؤال المقابلة: "كيف تحدد الفهارس غير المستخدمة؟"
-- PostgreSQL: إيجاد الفهارس غير المستخدمة
SELECT
schemaname || '.' || relname AS table,
indexrelname AS index,
pg_size_pretty(pg_relation_size(i.indexrelid)) AS size,
idx_scan AS scans
FROM pg_stat_user_indexes ui
JOIN pg_index i ON ui.indexrelid = i.indexrelid
WHERE idx_scan = 0
AND NOT indisunique
ORDER BY pg_relation_size(i.indexrelid) DESC;
نظرة المقابلة: عند مناقشة الفهارس، دائماً اعتبر المقايضة بين أداء القراءة (استعلامات أسرع) وأداء الكتابة (إدراج/تحديث أبطأ). أنظمة الإنتاج تحتاج هذا التوازن.
بعد ذلك، سنتدرب على أنماط مقابلات SQL الشائعة والحلول. :::