PostgreSQL: أساسيات فهارس PostgreSQL وتحسينها

آخر تحديث: 2026-08-26

1. ما ستتعلمه


2. القصة

Bob هو مسؤول قاعدة بيانات لمنصة تجارة إلكترونية. يبلغ المستخدمون أن صفحة بحث المنتجات تزداد بطئًا — حيث يستغرق المسح الكامل لعمود JSONB 3 ثوانٍ للاستعلام.

بعد أن أضاف Bob فهرس GIN على عمود JSONB، انخفض وقت البحث إلى 300 مللي ثانية. ثم لاحظ أن استعلامات المستخدمين النشطين بطيئة أيضًا، ومع ذلك فإن 90% من المستخدمين قد تم تعطيلهم بالفعل، لذا فإن بناء فهرس على الجدول بأكمله يهدر المساحة. استخدم فهرسًا جزئيًا يفهرس فقط الصفوف حيث status = 'active'، مما قلص حجم الفهرس بنسبة 80% وجعل الاستعلامات أسرع.


3. المفهوم: نظرة عامة على أنواع الفهارس

(1) أنواع الفهارس الستة

نوع الفهرس الاسم الكامل أنواع البيانات المناسبة حالة الاستخدام النموذجية
B-Tree شجرة متوازنة جميع الأنواع القابلة للترتيب المساواة، النطاق، الترتيب، LIKE البادئة
Hash جدول التجزئة جميع الأنواع استعلامات المساواة البسيطة
GIN الفهرس المعكوس المعمم المصفوفات، JSONB، البحث النصي الكامل استعلامات الاحتواء، البحث النصي الكامل
GiST شجرة البحث المعممة الهندسة، النطاقات، النص الكامل الاستعلامات المكانية، أقرب جار
BRIN فهرس نطاق الكتل الجداول الكبيرة ذات الأعمدة المرتبة بيانات السلاسل الزمنية، مسح النطاقات الزمنية
SP-GiST GiST المقسمة مكانيًا هياكل التقسيم غير المنتظمة أرقام الهواتف، التوجيه

(1) ▶ مثال

SQL
SELECT indexname, indexdef
FROM pg_indexes
WHERE tablename = 'orders';
TEXT 📖 للعرض فقط
 indexname              | indexdef
------------------------+--------------------------------------------------
 orders_pkey            | CREATE UNIQUE INDEX ... ON orders USING btree (order_id)
 idx_orders_customer_id | CREATE INDEX ... ON orders USING btree (customer_id)

(2) قرار اختيار نوع الفهرس

100%
flowchart TD
    A{نوع الاستعلام؟} -->|مساواة/نطاق/ترتيب| B[B-Tree]
    A -->|مساواة بسيطة| C[Hash]
    A -->|مصفوفة/JSONB يحتوي| D[GIN]
    A -->|مكاني/هندسي/أقرب| E[GiST]
    A -->|مسح تسلسلي لجدول كبير| F[BRIN]
    A -->|هيكل تقسيم غير منتظم| G[SP-GiST]

    B --> B1["نوع الفهرس الافتراضي<br/>90% من الحالات"]
    C --> C1["نادر الاستخدام<br/>B-Tree أفضل عادة"]
    D --> D1["JSONB @> ?<br/>مصفوفة @> <br/>tsvector @@ "]
    E --> E1["PostGIS<br/>تداخل النطاق"]
    F --> F1["10M+ صف سلاسل زمنية<br/>حجم صغير جدًا"]
    G --> G1["بادئة هاتف<br/>توجيه IP"]

    style A fill:#e1f5fe
    style B fill:#c8e6c9
    style D fill:#fff9c4
    style F fill:#fff9c4

4. المفهوم: فهرس B-Tree

(1) ميزات وحالات استخدام B-Tree

B-Tree هو نوع الفهرس الافتراضي في PostgreSQL؛ يدعم استعلامات المساواة والنطاق والترتيب و IS NULL و LIKE البادئة.

المعامل المدعوم مثال
المساواة WHERE col = 100
النطاق WHERE col > 100 AND col < 200
الترتيب ORDER BY col
IS NULL WHERE col IS NULL
LIKE البادئة WHERE col LIKE 'abc%'
BETWEEN WHERE col BETWEEN 1 AND 10
غير مدعوم السبب
LIKE '%abc' لا يمكن لحرف البدل البادئ استخدام ترتيب B-Tree
col::text = '100' عدم تطابق النوع؛ يحتاج فهرس تعبير
LOWER(col) = 'abc' نتيجة الدالة غير مفهرسة؛ تحتاج فهرس تعبير

(2) ▶ مثال

SQL
CREATE INDEX idx_orders_amount ON orders (amount);

CREATE INDEX idx_orders_date_amount ON orders (order_date, amount DESC);

Output:

TEXT 📖 للعرض فقط
CREATE TABLE

(3) ▶ مثال

SQL
CREATE UNIQUE INDEX idx_users_email ON users (email);

Output:

TEXT 📖 للعرض فقط
CREATE TABLE

يضمن فهرس UNIQUE تفرد البيانات وأداء الاستعلام معًا.


5. المفهوم: فهرس GIN

(1) مبدأ فهرس GIN

GIN (الفهرس المعكوس المعمم) هو فهرس معكوس: تعيين من عنصر إلى الصفوف التي تحتويه. يناسب استعلامات نمط "يحتوي".

النوع المعامل مثال
JSONB @> ? `? ?&`
مصفوفة @> <@ && WHERE tags @> ARRAY['sale']
tsvector @@ WHERE body @@ to_tsquery('postgres')

(4) ▶ مثال

SQL
CREATE INDEX idx_products_attrs ON products USING GIN (attrs);

SELECT product_id, name
FROM products
WHERE attrs @> '{"category": "electronics"}';
TEXT 📖 للعرض فقط
 product_id |    name
------------+------------
        101 | Laptop Pro
        205 | Smart Watch

(5) ▶ مثال

SQL
CREATE INDEX idx_products_tags ON products USING GIN (tags);

SELECT product_id, name
FROM products
WHERE tags @> ARRAY['summer', 'sale'];

Output:

TEXT 📖 للعرض فقط
  result  
----------
   42.50
(1 row)

(6) ▶ مثال

SQL
CREATE INDEX idx_articles_body ON articles USING GIN (to_tsvector('english', body));

SELECT id, title
FROM articles
WHERE to_tsvector('english', body) @@ to_tsquery('postgresql & index');

Output:

TEXT 📖 للعرض فقط
CREATE TABLE

(2) مقارنة GIN و B-Tree

البعد B-Tree GIN
نوع الاستعلام مساواة / نطاق احتواء / بحث
سرعة الكتابة سريعة بطيئة (يجب تحديث القوائم المعكوسة)
حجم الفهرس متوسط أكبر
الأنواع المناسبة عددية مصفوفة / JSONB / نص كامل
دعم الترتيب نعم لا

6. المفهوم: GiST / BRIN / SP-GiST / Hash

(1) فهرس GiST

GiST هو إطار شجرة بحث معمم يدعم استراتيجيات تقسيم مخصصة. استخدامه النموذجي هو البيانات المكانية.

حالة الاستخدام المعامل الامتداد
البيانات الهندسية && @ <@ PostGIS
أنواع النطاق && @> <@ مدمج
البحث النصي الكامل @@ مدمج

(7) ▶ مثال

SQL
CREATE INDEX idx_events_time_range ON events USING GiST (time_range);

SELECT event_id, title
FROM events
WHERE time_range && daterange('2025-01-01', '2025-03-01');

Output:

TEXT 📖 للعرض فقط
CREATE TABLE

(2) فهرس BRIN

BRIN (فهرس نطاق الكتل) يخزن معلومات ملخصة لكل كتلة بيانات (قيم الحد الأدنى/الأقصى). إنه مضغوط للغاية ويناسب الجداول الكبيرة المرتبة فعليًا.

البعد B-Tree BRIN
حجم الفهرس كبير صغير جدًا (حوالي 1/1000)
الدقة دقيقة تقريبية (قد تفحص بضع كتل إضافية)
تكلفة الصيانة مرتفعة منخفضة جدًا
حالة الاستخدام استعلامات عشوائية مسح نطاقات السلاسل الزمنية

(8) ▶ مثال

SQL
CREATE INDEX idx_logs_created_at ON logs USING BRIN (created_at)
  WITH (pages_per_range = 32);

SELECT count(*) FROM logs
WHERE created_at BETWEEN '2025-06-01' AND '2025-06-30';

Output:

TEXT 📖 للعرض فقط
 count 
-------
     5
(1 row)

(3) فهرس SP-GiST

يناسب SP-GiST هياكل التقسيم غير المتوازنة، مثل بادئات أرقام الهواتف وتوجيه IP.

(9) ▶ مثال

SQL
-- قم بتمكين امتداد btree_gist أولاً: CREATE EXTENSION IF NOT EXISTS btree_gist;
CREATE INDEX idx_customers_phone ON customers USING SP-GiST (phone prefix_range);

Output:

TEXT 📖 للعرض فقط
CREATE TABLE

(4) فهرس Hash

فهرس Hash يدعم فقط استعلامات المساواة البسيطة؛ لا يدعم النطاق أو الترتيب. قبل PostgreSQL 10، كانت فهارس Hash تعاني من مشكلة WAL (تم إصلاحها الآن)، لكن B-Tree هو الخيار الأفضل عمومًا.

البعد B-Tree Hash
استعلام المساواة سريع سريع
استعلام النطاق مدعوم غير مدعوم
الترتيب مدعوم غير مدعوم
WAL كامل كامل منذ PostgreSQL 10
التوصية الخيار الافتراضي نادر الاستخدام

7. المفهوم: ميزات الفهارس المتقدمة

(1) الفهرس الجزئي

يحتوي الفهرس الجزئي فقط على الصفوف التي تحقق شرط WHERE، مما يقلل حجم الفهرس وتكلفة الصيانة. هذه ميزة خاصة بـ PostgreSQL.

البعد فهرس كامل فهرس جزئي
الصفوف المضمنة جميع الصفوف الصفوف التي تحقق الشرط
حجم الفهرس كبير صغير
تكلفة الصيانة يُحدث عند كل كتابة فقط الصفوف ذات الصلة تُحدث
حالة الاستخدام استعلامات عام�� استعلامات تهتم فقط بمجموعة فرعية

(10) ▶ مثال

SQL
CREATE INDEX idx_users_active_email ON users (email)
WHERE status = 'active';

Output:

TEXT 📖 للعرض فقط
CREATE TABLE
SQL
SELECT email FROM users WHERE status = 'active' AND email = 'alice@example.com';

هذا الاستعلام يضرب الفهرس الجزئي. الاستعلام بدون شرط status = 'active' لن يضربه.

(11) ▶ مثال

SQL
CREATE INDEX idx_orders_pending ON orders (created_at)
WHERE shipped = false;

Output:

TEXT 📖 للعرض فقط
CREATE TABLE

(2) فهرس التعبير

عندما يلف شرط الاستعلام عمودًا بدالة أو عملية حسابية، لا يمكن استخدام الفهرس العادي. يبني فهرس التعبير الفهرس على النتيجة المحسوبة.

(12) ▶ مثال

SQL
CREATE INDEX idx_users_email_lower ON users (LOWER(email));

SELECT * FROM users WHERE LOWER(email) = 'alice@example.com';

Output:

TEXT 📖 للعرض فقط
CREATE TABLE

(13) ▶ مثال

SQL
CREATE INDEX idx_orders_date_trunc ON orders (DATE_TRUNC('day', created_at));

SELECT COUNT(*) FROM orders
WHERE DATE_TRUNC('day', created_at) = '2025-06-15'::date;

Output:

TEXT 📖 للعرض فقط
 count 
-------
     5
(1 row)

(3) الفهرس المركب والبادئة اليسرى

أنماط الاستعلام التي يمكن للفهرس المركب (a, b, c) خدمتها:

شرط الاستعلام يضرب؟ السبب
WHERE a = 1 نعم البادئة اليسرى
WHERE a = 1 AND b = 2 نعم البادئة اليسرى
WHERE a = 1 AND b = 2 AND c = 3 نعم تطابق كامل
WHERE b = 2 لا العمود الأيسر مفقود
WHERE b = 2 AND c = 3 لا العمود الأيسر مفقود
WHERE a = 1 AND c = 3 جزئي فقط العمود a مستخدم؛ c غير متصل

(14) ▶ مثال

SQL
CREATE INDEX idx_orders_customer_date
ON orders (customer_id, order_date DESC);

Output:

TEXT 📖 للعرض فقط
CREATE TABLE

(4) بناء الفهرس عبر الإنترنت باستخدام CONCURRENTLY

بناء الفهرس يأخذ قفلًا حصريًا افتراضيًا، مما يمنع الكتابة. CONCURRENTLY لا يمنع الكتابة، لكنه يبني بشكل أبطأ.

الطريقة القفل يمنع الكتابة السرعة داخل معاملة؟
CREATE INDEX قفل حصري يمنع سريع نعم
CREATE INDEX CONCURRENTLY قفل مشترك لا يمنع بطيء لا

(15) ▶ مثال

SQL
CREATE INDEX CONCURRENTLY idx_orders_region
ON orders (region);

Output:

TEXT 📖 للعرض فقط
CREATE TABLE
⚠️ ملاحظة: لا يمكن تنفيذ CONCURRENTLY داخل كتلة معاملة.


8. المفهوم: خطة تنفيذ EXPLAIN

(1) أساسيات EXPLAIN

الأمر الوصف ينفذ الاستعلام؟
EXPLAIN عرض خطة التنفيذ لا
EXPLAIN ANALYZE تنفيذ وعرض التوقيت الفعلي نعم
EXPLAIN BUFFERS عرض إصابات المخزن المؤقت نعم
EXPLAIN (FORMAT JSON) إخراج بتنسيق JSON لا

(16) ▶ مثال

SQL
EXPLAIN
SELECT * FROM orders WHERE customer_id = 1;
TEXT 📖 للعرض فقط
                              QUERY PLAN
----------------------------------------------------------------------
 Index Scan using idx_orders_customer_id on orders  (cost=0.29..8.31 rows=1 width=72)
   Index Cond: (customer_id = 1)

(17) ▶ مثال

SQL
EXPLAIN ANALYZE
SELECT * FROM orders WHERE customer_id = 1;
TEXT 📖 للعرض فقط
                              QUERY PLAN
----------------------------------------------------------------------
 Index Scan using idx_orders_customer_id on orders
   (cost=0.29..8.31 rows=1 width=72) (actual time=0.015..0.016 rows=2 loops=1)
   Index Cond: (customer_id = 1)
 Planning Time: 0.085 ms
 Execution Time: 0.032 ms

(2) أنواع المسح الرئيسية

نوع المسح المعنى يستخدم الفهرس؟
Seq Scan مسح تسلسلي كامل للجدول لا
Index Scan مسح الفهرس (جلب صفوف الجدول) نعم
Index Only Scan مسح الفهرس فقط (بدون بحث في الجدول) نعم (فهرس مغطٍ)
Bitmap Scan مسح الصورة النقطية (دفعات كبيرة) جزئي
Parallel Seq Scan مسح تسلسلي متوازي للجدول لا

(18) ▶ مثال

SQL
EXPLAIN
SELECT customer_id FROM orders WHERE customer_id = 1;
TEXT 📖 للعرض فقط
 Index Only Scan using idx_orders_customer_id on orders
   Index Cond: (customer_id = 1)

يتم قراءة الفهرس فقط — بدون بحث في الجدول عن صفوف البيانات — مما يعطي أفضل أداء.


9. صيانة الفهارس وفشلها

(1) إعادة بناء REINDEX

بعد العديد من عمليات الإدر��ج/الحذف مع مرور الوقت، قد ينتفخ الفهرس؛ REINDEX يعيد بنائه لاستعادة المساحة.

الطريقة الوصف القفل
REINDEX INDEX idx إعادة بناء فهرس واحد قفل حصري
REINDEX TABLE tbl إعادة بناء جميع فهارس الجدول قفل حصري
REINDEX INDEX CONCURRENTLY idx إعادة بناء عبر الإنترنت (PG 12+) لا يمنع

(19) ▶ مثال

SQL
REINDEX INDEX idx_orders_customer_id;

REINDEX INDEX CONCURRENTLY idx_orders_customer_id;

Output:

TEXT 📖 للعرض فقط
-- SQL statement executed successfully

(2) سيناريوهات فشل الفهرس الشائعة

السيناريو مثال الإصلاح
دالة تلف عمودًا WHERE LOWER(col) = 'x' فهرس تعبير
تحويل نوع ضمني WHERE varchar_col = 123 استخدام أنواع متسقة
حرف بدل بادئ WHERE col LIKE '%abc' GIN / pg_trgm
شرط OR WHERE a=1 OR b=2 فهارس منفصلة أو UNION
إحصائيات قدي��ة بعد تغيير كبير في البيانات ANALYZE
مخالفة البادئة اليسرى فهرس مركب (a,b) يُستعلم بـ b ضبط الفهرس أو الاستعلام
عدم تطابق شرط الفهرس الجزئي WHERE status='active' يستعلم الكل إسقاط WHERE أو بناء فهرس كامل

(20) ▶ مثال

SQL
SELECT * FROM users WHERE phone = 13800138000;

SELECT * FROM users WHERE phone = '13800138000';

Output:

TEXT 📖 للعرض فقط
 id | name     | value 
----+----------+-------
  1 | example  | 42
(1 row)

phone هو VARCHAR. التحويل الضمني في السطر الأول يتسبب في عدم استخدام الفهرس؛ السطر الثاني يضرب الفهرس.


10. مثال شامل

خطة تحسين الفهرس لـ Bob — بحث منتجات JSONB + فهرس جزئي للمستخدمين النشطين + فهرس مركب + فهرس BRIN للسلاسل الزمنية:

SQL
CREATE INDEX CONCURRENTLY idx_products_attrs_gin
  ON products USING GIN (attrs);

CREATE INDEX CONCURRENTLY idx_users_active_email
  ON users (email, last_login_at)
  WHERE status = 'active';

CREATE INDEX CONCURRENTLY idx_orders_customer_date
  ON orders (customer_id, order_date DESC);

CREATE INDEX CONCURRENTLY idx_audit_log_created_brin
  ON audit_log USING BRIN (created_at)
  WITH (pages_per_range = 32);

EXPLAIN ANALYZE
SELECT p.product_id, p.name, p.attrs
FROM products p
WHERE p.attrs @> '{"category": "electronics", "in_stock": true}';

EXPLAIN ANALYZE
SELECT user_id, email
FROM users
WHERE status = 'active'
  AND email LIKE 'alice%'
ORDER BY last_login_at DESC
LIMIT 10;

11. تدفق التنفيذ

تدفق اختيار نوع الفهرس وتحسينه:

100%
flowchart TD
    A[وجدت استعلامًا بطيئًا] --> B[EXPLAIN ANALYZE]
    B --> C{نوع المسح؟}
    C -->|Seq Scan| D{هل يوجد فهرس مناسب؟}
    C -->|Index Scan| E[الفهرس يضرب<br/>حسّن الاستعلام/الفهرس]
    D -->|لا| F{نوع البيانات؟}
    D -->|نعم لكن لا يضرب| G[تحقق من إبطال الفهرس]
    F -->|عددي/ترتيب| H[أنشئ B-Tree]
    F -->|JSONB/مصفوفة| I[أنشئ GIN]
    F -->|هندسي/نطاق| J[أنشئ GiST]
    F -->|جدول كبير تسلسلي| K[أنشئ BRIN]
    F -->|مجموعة فرعية فقط| L[أنشئ فهرس جزئي]
    F -->|دالة/محسوب| M[أنشئ فهرس تعبير]
    H --> N[نشر CONCURRENTLY]
    I --> N
    J --> N
    K --> N
    L --> N
    M --> N

    style B fill:#e1f5fe
    style G fill:#ffccbc
    style N fill:#c8e6c9

❓ أسئلة شائعة

س هل "المزيد من الفهارس" أفضل دائمًا؟
ج لا. كل فهرس يضيف عبء كتابة (يجب على INSERT/UPDATE/DELETE صيانة الفهرس) ومساحة تخزين. أنشئ الفهارس فقط للاستعلامات عالية التردد، وتحقق دوريًا من الاستخدام باستخدام pg_stat_user_indexes.
س ماذا لو فشل البناء باستخدام CONCURRENTLY؟
ج بناء CONCURRENTLY الفاشل يترك فهرس INVALID. ابحث عنه باستخدام SELECT indexname FROM pg_indexes WHERE indexdef LIKE '%INVALID%' أو \d+ tbl، ثم أسقطه باستخدام DROP INDEX.
س لماذا لا يستخدم استعلام LIKE الفهرس؟
ج LIKE 'abc%' يمكنه استخدام B-Tree؛ LIKE '%abc' بحرف بدل بادئ لا يمكنه — يحتاج GIN بالإضافة إلى امتداد pg_trgm.
س ما السيناريوهات التي تناسب فهرس BRIN؟
ج الجداول الكبيرة المرتبة فعليًا (مثل جداول السجلات المضافة فقط والمكتوبة بترتيب زمني). إذا لم تكن للبيانات علاقة بالترتيب الفعلي، يعمل التصفية التقريبية لـ BRIN بشكل سيء ولا ينصح به.
س هل يؤثر الفهرس الجزئي على أداء الكتابة؟
ج أقل من الفهرس الكامل. الصفوف التي لا تحقق شرط WHERE لا تحتاج إلى تحديث فهرسها الجزئي عند الكتابة، مما يوفر I/O.
س هل يمكن للفهرس المركب (a, b) دعم WHERE a=1 ORDER BY b؟
ج نعم. الفهرس المركب مرتب حسب a ثم b، لذا فهو يلبي كلاً من مرشح WHERE و ORDER BY، متجنبًا فرزًا إضافيًا.

📖 ملخص


📝 تمارين

  1. ⭐ أنشئ فهرس B-Tree على عمود customer_id في جدول orders، وتحقق باستخدام EXPLAIN من أن الاستعلام يضرب الفهرس.
  2. ⭐ أنشئ فهرس GIN على عمود JSONB في جدول products، ولاحظ كيف تتغير خطة التنفيذ لاستعلام @>.
  3. ⭐⭐ أنشئ فهرسًا جزئيًا: فهرس فقط عمود created_at للطلبات حيث status = 'pending'، وقارن فرق الحجم مع فهرس كامل.
  4. ⭐⭐ أنشئ فهرس تعبير على LOWER(email)، وتحقق من أن استعلامًا غير حساس لحالة الأحرف يضرب الفهرس�� ثم أنشئ فهرسًا مركبًا (region, created_at DESC) لدعم الاستعلامات المرتبة حسب المنطقة والوقت.
  5. ⭐⭐⭐ لجدول سجلات بعشرات الملايين من الصفوف، صمم خطة مدمجة من فهرس BRIN + فهرس B-Tree مركب + فهرس جزئي، واستخدم EXPLAIN ANALYZE لمقارنة وقت الاستعلام قبل وبعد التحسين.
Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%