PostgreSQL: نظام إضافات PostgreSQL وFDW والبحث المتجهي

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

1. ما ستتعلمه


2. القصة

تحتاج شركة Alice للتجارة الإلكترونية إلى القيام بثلاثة أشياء: (1) دمج توصيات منتجات الذكاء الاصطناعي، مما يتطلب بحثًا متجهيًا؛ (2) ترحيل بيانات MySQL القديمة إلى PG، مما يتطلب استعلامات عبر قواعد البيانات؛ (3) تسريع البحث التقريبي البطيء، مما يتطلب pg_trgm. وجدت أن نظام إضافات PostgreSQL يحل كل شيء في مكان واحد — pgvector يخزن تضمينات المنتجات، و postgres_fdw يتصل بـ MySQL للترحيل، و pg_trgm يعزز أداء البحث التقريبي. قاعدة بيانات واحدة تستبدل Elasticsearch + الوسيط + خدمة توصية مبنية ذاتيًا.


3. المفهوم: نظرة عامة على آلية الإضافات

(1) ما هي إضافة PostgreSQL

الإضافة هي نظام المكونات الإضافية في PostgreSQL — تجمع كائنات SQL المرتبطة (الدوال، الأنواع، العوامل، طرق الفهرس) كوحدة واحدة يمكن تثبيتها وإلغاء تثبيتها بأمر واحد.

SQL
-- سرد الإضافات المتاحة
SELECT name, default_version, installed_version, comment
FROM pg_available_extensions
ORDER BY name;

(2) أوامر إدارة الإضافات

الأمر الغرض
CREATE EXTENSION ext_name تثبيت إضافة
CREATE EXTENSION IF NOT EXISTS ext_name تثبيت متكرر آمن
CREATE EXTENSION ext_name VERSION '1.2' تثبيت إصدار محدد
DROP EXTENSION ext_name إلغاء تثبيت إضافة (CASCADE للاحتفاظ بال��كوين)
ALTER EXTENSION ext_name UPDATE TO '2.0' ترقية إضافة
\dx (psql) سرد الإضافات المثبتة

(3) المتطلبات الأساسية لتثبيت الإضافات

الشرط الوصف
ملف مكتبة مشتركة .so / .dll يجب أن يكون في shared_preload_libraries أو dynamic_library_path
ملف تحكم extension_name.control في SHAREDIR/extension/
سكريبت SQL extension_name--version.sql يعرف الكائنات
الصلاحيات تحتاج CREATE على قاعدة البيانات الحالية + مستخدم مميز (لبعض الإضافات)

4. العملية: الإضافات الشائعة

(1) ▶ مثال

SQL
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";

-- توليد UUID v4 (عشوائي)
SELECT uuid_generate_v4();

-- توليد UUID v1 (معتمد على الوقت)
SELECT uuid_generate_v1();

-- استخدام كقيمة افتراضية لعمود
CREATE TABLE api_keys (
    id          UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
    user_id     BIGINT NOT NULL,
    key_name    TEXT,
    created_at  TIMESTAMPTZ DEFAULT now()
);

Output:

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

(2) ▶ مثال

SQL
CREATE EXTENSION IF NOT EXISTS pgcrypto;

-- تجزئة كلمة المرور مع ملح
SELECT crypt('MySecret123', gen_salt('bf'));

-- التحقق من كلمة المرور
SELECT crypt('MySecret123', stored_hash) = stored_hash AS is_match;

-- تشفير AES
SELECT encode(encrypt('credit card data'::bytea,
    'secret_key_16bytes'::bytea, 'aes'), 'hex');

-- توليد رمز عشوائي
SELECT encode(gen_random_bytes(32), 'hex');

Output:

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

(3) ▶ مثال

SQL
CREATE EXTENSION IF NOT EXISTS pg_trgm;

-- عرض درجة التشابه
SELECT similarity('PostgreSQL', 'Postgres');

-- فهرس GIN trigram للبحث التقريبي السريع
CREATE INDEX idx_products_name_trgm ON products
    USING GIN (name gin_trgm_ops);

-- بحث تقريبي مع عتبة
SELECT name, similarity(name, 'iphon') AS score
FROM products
WHERE name % 'iphon'
ORDER BY score DESC;
TEXT 📖 للعرض فقط
     name      | score
---------------+-------
 iPhone 15 Pro |  0.42
 iPhone 14     |  0.38
(2 rows)

(4) ▶ مثال

SQL
-- يجب أن يكون في shared_preload_libraries أولاً
-- postgresql.conf: shared_preload_libraries = 'pg_stat_statements'

CREATE EXTENSION IF NOT EXISTS pg_stat_statements;

-- أفضل 10 استعلامات حسب وقت التنفيذ الإجمالي
SELECT query,
       calls,
       round(total_exec_time::numeric, 2) AS total_ms,
       round(mean_exec_time::numeric, 2)  AS avg_ms,
       rows
FROM pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 10;

-- إعادة تعيين الإحصائيات
SELECT pg_stat_statements_reset();

Output:

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

(5) ▶ مثال

SQL
CREATE EXTENSION IF NOT EXISTS postgis;

-- تخزين هندسة النقطة
CREATE TABLE stores (
    id       SERIAL PRIMARY KEY,
    name     TEXT,
    location GEOMETRY(POINT, 4326)
);

-- إدراج إحداثيات (خط طول، خط عرض)
INSERT INTO stores (name, location)
VALUES ('Dubai Mall', ST_SetSRID(ST_MakePoint(55.2796, 25.1972), 4326));

-- العثور على متاجر ضمن 5 كم
SELECT name,
    ST_Distance(location::geography,
        ST_SetSRID(ST_MakePoint(55.2700, 25.2000), 4326)::geography
    ) AS distance_m
FROM stores
WHERE ST_DWithin(location::geography,
    ST_SetSRID(ST_MakePoint(55.2700, 25.2000), 4326)::geography, 5000);

Output:

TEXT 📖 للعرض فقط
INSERT 0 1

5. المفهوم: Foreign Data Wrappers

(1) معمارية FDW

100%
flowchart LR
    A["PG محلي<br/>postgres_fdw"] -->|"CREATE SERVER"| B["PG بعيد<br/>(أو MySQL/Oracle)"]
    A -->|"CREATE SERVER"| C["file_fdw<br/>(ملفات CSV/سجل)"]
    A -->|"CREATE SERVER"| D["FDW أخرى<br/>(Redis/MongoDB...)"]
    B --> E["IMPORT FOREIGN SCHEMA"]
    C --> F["CREATE FOREIGN TABLE"]
    D --> G["JOIN عبر الأنظمة"]
اسم FDW مصدر البيانات الهدف حالة الاستخدام
postgres_fdw PostgreSQL بعيد استعلامات عبر قواعد البيانات، ترحيل البيانات
mysql_fdw MySQL بعيد ترحيل MySQL→PG
file_fdw ملفات CSV محلية تحليل السجلات، استيراد البيانات
redis_fdw Redis استعلامات التخزين المؤقت
mongo_fdw MongoDB استعلامات المستندات

(2) خطوات تكوين FDW

الخطوة الأمر
1. تثبيت الإضافة CREATE EXTENSION postgres_fdw
2. إنشاء SERVER CREATE SERVER remote FOREIGN DATA WRAPPER postgres_fdw OPTIONS (...)
3. إنشاء USER MAPPING CREATE USER MAPPING FOR local_user SERVER remote OPTIONS (...)
4. إنشاء FOREIGN TABLE CREATE FOREIGN TABLE ft_xxx SERVER remote OPTIONS (...)
5. أو استيراد SCHEMA كامل IMPORT FOREIGN SCHEMA public LIMIT TO (orders) FROM SERVER remote INTO remote_schema

6. العملية: postgres_fdw استعلام عبر قواعد البيانات

(6) ▶ مثال

SQL
-- الخطوة 1: تثبيت الإضافة
CREATE EXTENSION IF NOT EXISTS postgres_fdw;

-- الخطوة 2: إنشاء اتصال الخادم
CREATE SERVER legacy_db
    FOREIGN DATA WRAPPER postgres_fdw
    OPTIONS (host '192.168.1.100', port '5432', dbname 'legacy');

-- الخطوة 3: ربط المستخدم المحلي ببيانات اعتماد بعيدة
CREATE USER MAPPING FOR current_user
    SERVER legacy_db
    OPTIONS (user 'admin', password 'secret123');

-- الخطوة 4: استيراد المخطط الخارجي
IMPORT FOREIGN SCHEMA public
    LIMIT TO (users, products, orders)
    FROM SERVER legacy_db
    INTO legacy_schema;

-- الآن استعلم عن الجداول البعيدة كما لو كانت محلية
SELECT u.name, COUNT(o.id) AS order_count
FROM legacy_schema.users u
JOIN legacy_schema.orders o ON u.id = o.user_id
GROUP BY u.name;

Output:

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

(7) ▶ مثال

SQL
-- محلي: كتالوج منتجات PG الجديد
-- بعيد: بيانات طلبات MySQL القديمة عبر postgres_fdw

SELECT p.name,
       SUM(loi.quantity) AS total_sold,
       SUM(loi.quantity * loi.unit_price) AS revenue
FROM products p
JOIN legacy_schema.order_items loi ON p.id = loi.product_id
WHERE p.category = 'Electronics'
GROUP BY p.name
ORDER BY revenue DESC;

Output:

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

(8) ▶ مثال

SQL
CREATE EXTENSION IF NOT EXISTS file_fdw;

CREATE SERVER csv_server
    FOREIGN DATA WRAPPER file_fdw;

CREATE FOREIGN TABLE access_logs_csv (
    ip_address    TEXT,
    request_time  TIMESTAMP,
    method        TEXT,
    path          TEXT,
    status_code   INT,
    response_time NUMERIC
) SERVER csv_server
OPTIONS (filename '/var/log/nginx/access.csv', format 'csv', header 'true');

-- تحليل سجلات nginx بـ SQL
SELECT path,
       COUNT(*) AS hits,
       AVG(response_time) AS avg_ms
FROM access_logs_csv
WHERE status_code = 200
GROUP BY path
ORDER BY hits DESC
LIMIT 20;

Output:

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

(9) ▶ مثال

SQL
-- ترحيل المستخدمين من القديم إلى المحلي
INSERT INTO users (name, email, created_at)
SELECT name, email, created_at
FROM legacy_schema.users
WHERE id NOT IN (SELECT legacy_id FROM users);

-- استخدام dblink للمزامنة التدريجية
CREATE EXTENSION IF NOT EXISTS dblink;

SELECT dblink_connect('legacy', 'host=192.168.1.100 dbname=legacy user=admin password=secret123');

SELECT * FROM dblink('legacy',
    'SELECT id, name, email FROM users WHERE created_at > now() - interval ''1 day'''
) AS t(id BIGINT, name TEXT, email TEXT);

Output:

TEXT 📖 للعرض فقط
INSERT 0 1

7. المفهوم: pgvector البحث المتجهي

(1) كيفية عمل البحث المتجهي

تقوم نماذج الذكاء الاصطناعي بترميز النص/الصور إلى متجهات عالية الأبعاد (تضمينات)، وتمثل المسا��ة بين المتجهات التشابه الدلالي. يتيح pgvector لـ PostgreSQL تخزين واسترجاع المتجهات أصليًا.

مقياس المسافة الصيغة حالة الاستخدام
مسافة L2 (<=>) المسافة الإقليدية المسافة المكانية
الجداء الداخلي (<#>) الجداء النقطي المتجهات المعيارية
مسافة جيب التمام (<=>) 1 - cos(θ) التشابه الدلالي

(2) اختيار فهرس pgvector

100%
flowchart TD
    A["تم إنشاء عمود المتجه"] --> B{"الصفوف < 10K؟"}
    B -->|نعم| C["بحث دقيق<br/>(لا حاجة لفهرس)"]
    B -->|لا| D{"متطلب الاستدعاء؟"}
    D -->|"استدعاء عال (> 99%)"| E["IVFFlat<br/>(probes=lists)"]
    D -->|"سريع + استدعاء جيد"| F["HNSW<br/>(ضبط ef_search)"]
    E --> G["CREATE INDEX ... USING ivfflat<br/>(vector_cosine_ops)"]
    F --> H["CREATE INDEX ... USING hnsw<br/>(vector_cosine_ops)"]
الفهرس سرعة البناء سرعة الاستعلام الاستدعاء الأفضل لـ
بدون فهرس (قوة غاشمة) غير متاح بطيء 100% < 10K
IVFFlat متوسط سريع 95-99% 10K-1M
HNSW بطيء الأسرع 97-99.5% 100K-10M+

8. العملية: pgvector عمليًا

(10) ▶ مثال

SQL
-- تثبيت pgvector
CREATE EXTENSION IF NOT EXISTS vector;

-- جدول منتجات مع متجه تضمين (1536 بعدًا لـ OpenAI)
CREATE TABLE products_vec (
    id          SERIAL PRIMARY KEY,
    name        TEXT NOT NULL,
    category    TEXT,
    price       NUMERIC(10,2),
    embedding   vector(1536)
);

Output:

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

(11) ▶ مثال

SQL
-- إدراج منتج مع تضمين
INSERT INTO products_vec (name, category, price, embedding)
VALUES ('Wireless Headphones', 'Electronics', 79.99,
    '[0.012, -0.034, 0.056, ...]'::vector);

-- العثور على أفضل 5 منتجات متشابهة بمسافة جيب التمام
SELECT p.id, p.name, p.category, p.price,
       p.embedding <=> '[0.015, -0.030, 0.050, ...]'::vector AS distance
FROM products_vec p
ORDER BY p.embedding <=> '[0.015, -0.030, 0.050, ...]'::vector
LIMIT 5;

Output:

TEXT 📖 للعرض فقط
INSERT 0 1

(12) ▶ مثال

SQL
-- فهرس HNSW للبحث التقريبي السريع
CREATE INDEX idx_products_vec_hnsw ON products_vec
    USING hnsw (embedding vector_cosine_ops)
    WITH (m = 16, ef_construction = 64);

-- ضبط دقة البحث مقابل السرعة
SET hnsw.ef_search = 100;

-- استعلام مع الفهرس (أسرع بكثير على مجموعات البيانات الكبيرة)
SELECT name,
       embedding <=> '[0.015, -0.030, 0.050, ...]'::vector AS distance
FROM products_vec
ORDER BY embedding <=> '[0.015, -0.030, 0.050, ...]'::vector
LIMIT 10;

Output:

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

(13) ▶ مثال

SQL
-- تخزين تضمينات المنتجات من نموذج الذكاء الاصطناعي
INSERT INTO products_vec (name, category, price, embedding)
VALUES
    ('Running Shoes',     'Sports',   129.99, array_to_vec(ARRAY[0.1,0.2,0.3])::vector),
    ('Yoga Mat',          'Sports',    39.99, array_to_vec(ARRAY[0.11,0.19,0.31])::vector),
    ('Bluetooth Speaker', 'Electronics', 49.99, array_to_vec(ARRAY[0.5,0.1,0.2])::vector);

-- شاهد المستخدم "Running Shoes"، أوصِ بعناصر مشابهة
WITH target AS (
    SELECT embedding FROM products_vec WHERE name = 'Running Shoes'
)
SELECT p.name, p.category, p.price,
       p.embedding <=> (SELECT embedding FROM target) AS similarity
FROM products_vec p
WHERE p.name != 'Running Shoes'
ORDER BY similarity
LIMIT 3;

Output:

TEXT 📖 للعرض فقط
INSERT 0 1

9. العملية: أفضل ممارسات إدارة الإضافات

(1) إدارة الإصدارات

SQL
-- التحقق من إصدار الإضافة الحالي
SELECT extname, extversion FROM pg_extension ORDER BY extname;

-- ترقية إضافة
ALTER EXTENSION pgvector UPDATE TO '0.7.0';

-- ترقية جميع الإضافات
SELECT extname,
       installed_version,
       default_version
FROM pg_available_extensions
WHERE installed_version IS NOT NULL
  AND installed_version != default_version;

(2) إدارة الصلاحيات

السيناريو الممارسة الموصى بها
تثبيت إضافة في الإنتاج مستخدم مميز يشغل CREATE EXTENSION
استخدام مستخدم عادي GRANT USAGE ON SCHEMA / صلاحية تنفيذ الدالة
اتصال FDW USER MAPPING يخزن بيانات الاعتماد، لا تصلب كلمات المرور
ترقية إضافة اختبر في staging أولاً، ثم شغل في الإنتاج

10. مثال شامل

SQL
-- إعداد كامل: إضافات + FDW + pgvector للتجارة الإلكترونية بالذكاء الاصطناعي

-- الخطوة 1: تثبيت الإضافات الأساسية
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS pgcrypto;
CREATE EXTENSION IF NOT EXISTS pg_trgm;
CREATE EXTENSION IF NOT EXISTS pgvector;

-- الخطوة 2: كتالوج منتجات مع بحث متجهي
CREATE TABLE products_ai (
    id          UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
    name        TEXT NOT NULL,
    category    TEXT,
    price       NUMERIC(10,2),
    attrs       JSONB DEFAULT '{}',
    embedding   vector(1536)
);

-- الخطوة 3: فهرس GIN للبحث التقريبي بالاسم
CREATE INDEX idx_products_name_trgm ON products_ai
    USING GIN (name gin_trgm_ops);

-- الخطوة 4: فهرس HNSW لتشابه المتجهات
CREATE INDEX idx_products_embedding ON products_ai
    USING hnsw (embedding vector_cosine_ops)
    WITH (m = 16, ef_construction = 64);

-- الخطوة 5: الاتصال بقاعدة البيانات القديمة عبر FDW
CREATE EXTENSION IF NOT EXISTS postgres_fdw;
CREATE SERVER legacy_db FOREIGN DATA WRAPPER postgres_fdw
    OPTIONS (host '192.168.1.50', port '5432', dbname 'legacy_shop');
CREATE USER MAPPING FOR current_user SERVER legacy_db
    OPTIONS (user 'migrate_user', password 'secure_pass');
IMPORT FOREIGN SCHEMA public LIMIT TO (old_products, old_users)
    FROM SERVER legacy_db INTO legacy;

-- الخطوة 6: ترحيل وتحويل البيانات
INSERT INTO products_ai (name, category, price, attrs)
SELECT name, category, price,
       jsonb_build_object('weight_kg', weight, 'color', color)
FROM legacy.old_products
WHERE active = true;

-- الخطوة 7: بحث مدمج تقريبي + متجهي
SELECT p.name, p.price,
       similarity(p.name, 'wireless earbuds') AS text_score,
       p.embedding <=> '[0.015,-0.030,0.050]'::vector AS vec_dist
FROM products_ai p
WHERE p.name % 'wireless earbuds'
ORDER BY vec_dist ASC
LIMIT 5;

❓ أسئلة شائعة

س هل يحتاج CREATE EXTENSION إلى مستخدم مميز؟
ج معظم الإضافات تحتاج مستخدمًا مميزًا لأنها تحمل مكتبة ديناميكية C. PG 14+ يسمح ببعض الإضافات عبر GRANT CREATE ON DATABASE + آلية الإضافة الموثوقة.
س ما الحد الأقصى للأبعاد التي يدعمها عمود المتجه في pgvector؟
ج يدعم pgvector حتى 16,000 بعدًا (0.7.0+)، لكن اختر بناءً على مخرجات النموذج (OpenAI 1536، Cohere 1024، إلخ). الأبعاد الأعلى تجعل الفهارس أبطأ.
س كيف أداء استعلام postgres_fdw؟
ج FDW له عبء شبكة — الاستعلامات الب��يطة تضيف حوالي 2-5 مللي ثانية تأخير. تعيين use_remote_estimate=on يجعل البعيد يولد تقديرات التكلفة ويحسن جودة الخطة. للترحيل الجماعي، فضل COPY على FDW.
س أيهما أختار، HNSW أم IVFFlat؟
ج لأقل من 100K صف، لا حاجة لفهرس؛ لـ 100K-1M، اختر IVFFlat (بناء سريع)؛ لأكثر من 100K مع احتياجات تأخير منخفض، اختر HNSW (أسرع استعلام). HNSW يبنى ببطء لكنه يستعلم أسرع بكثير من IVFFlat.
س هل يبطئ فهرس pg_trgm GIN عملية INSERT؟
ج نعم. فهرس trigram يقسم رموزًا كثيرة؛ عبء الكتابة حوالي 3-5 أضعاف B-tree العادي. أفضل لسيناريوهات البحث كثيف القراءة قليل الكتابة.
س هل يمكن لـ FDW الكتابة إلى جداول بعيدة؟
ج postgres_fdw يدعم INSERT/UPDATE/DELETE على الجداول البعيدة. file_fdw للقراءة فقط. الكتابة إلى جداول بعيدة تحمل خطر المعاملات الموزعة — احتفظ بها للاستعلامات القراءة فقط أو الترحيل الجماعي.

📖 ملخص


📝 تمارين

  1. ⭐ قم بتثبيت إضافتي uuid-ossp و pgcrypto، أنشئ جدول api_tokens باستخدام UUID كمفتاح أساسي و crypt() لتخزين تجزئة كلمة المرور، واكتب استعلامًا يتحقق من كلمة المرور.

  2. ⭐⭐ قم بتكوين postgres_fdw للاتصال بقاعدة بيانات PG بعيدة (استخدم Docker للمحاكاة)، استورد جدول products البعيد، واكتب استعلام JOIN عبر قواعد البيانات: جدول orders المحلي + جدول products البعيد.

  3. ⭐⭐⭐ صمم بحث منتجات بمحرك مزدوج: pg_trgm يتعامل مع البحث النصي التقريبي، pgvector يتعامل مع البحث المتجهي الدلالي. اكتب دالة بحث مدمجة search_products(keyword TEXT, query_vec vector, limit_count INT) تدمج درجة النص ومسافة المتجه للترتيب، واختبر كيف تختلف النتائج عبر الأوزان المختلفة.

Web-Tutorial.com

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

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

100%