PostgreSQL: محرك البحث النصي الكامل في PostgreSQL

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

1. ما ستتعلمه


2. القصة

منصة التجارة الإلكترونية لـ Charlie تحتوي على 500,000 سجل منتج، ويحتاج المستخدمون للبحث عن المنتجات. بدأ باستخدام LIKE '%red shoes%'، لكن البحث عن "red shoes" أرجع فقط المنتجات التي يحتوي وصفها بالصدفة على السلسلة المتتالية الدقيقة "red shoes"، واستغرق الاستعلام 3 ثوانٍ. بعد التبديل إلى البحث النصي الكامل في PostgreSQL:


3. المفهوم: المفاهيم الأساسية للبحث النصي الكامل

(1) أربعة مفاهيم أساسية

المفهوم الوصف مكافئ PostgreSQL
المستند المحتوى النصي المراد البحث فيه tsvector
الاستعلام مصطلحات بحث المستخدم tsquery
القاموس يعرف كيفية معالجة الكلمات (كلمات التوقف، التجذيع) فهرس نظام pg_ts_dict
التكوين يجمع المحلل والقواميس فهرس نظام pg_ts_config

(2) خط أنابيب معالجة البحث النصي الكامل

100%
flowchart LR
    A[نص خام] --> B[محلل<br/>تجزئة]
    B --> C[قاموس<br/>تجذيع + كلمات توقف]
    C --> D[tsvector<br/>مفردات مرتبة]
    D --> E[فهرس GIN<br/>بحث سريع]
    F[استعلام المستخدم] --> G[to_tsquery<br/>تحليل وتطبيع]
    G --> H[tsquery<br/>مفردات + عوامل]
    E --> I["عامل @@<br/>مطابقة"]
    H --> I
    I --> J[ts_rank<br/>تسجيل]
    J --> K[ts_headline<br/>تمييز]
    K --> L[نتائج]

(1) ▶ مثال

SQL
SELECT * FROM ts_debug('english', 'The red shoes are beautiful');
TEXT 📖 للعرض فقط
 alias | descriptor | token  |  dictionaries  | dictionary | lexemes 
-------+------------+--------+----------------+------------+---------
 ascii | word       | The    | {english_stem}  | english_stem| {}
 ascii | word       | red    | {english_stem}  | english_stem| {red}
 ascii | word       | shoes  | {english_stem}  | english_stem| {shoe}
 ascii | word       | are    | {english_stem}  | english_stem| {}
 ascii | word       | beautiful | {english_stem}| english_stem| {beauti}

4. المفهوم: tsvector و tsquery

(1) tsvector — النتيجة المعالجة مسبقًا للمستند

tsvector هو قائمة مرتبة ومزالة التكرار من المفردات، كل منها مع معلومات الموضع.

(2) ▶ مثال

SQL
SELECT to_tsvector('english', 'The red shoes are beautiful shoes');
TEXT 📖 للعرض فقط
'beauti':5 'red':2 'shoe':3,6
الخطوة الإدخال الإخراج
تجزئة The red shoes are beautiful shoes [The, red, shoes, are, beautiful, shoes]
إزالة كلمات التوقف [The, red, shoes, are, beautiful, shoes] [red, shoes, beautiful, shoes]
تجذيع [red, shoes, beautiful, shoes] [red, shoe, beauti, shoe]
إزالة التكرار + الموضع [red, shoe, beauti, shoe] 'beauti':5 'red':2 'shoe':3,6

(2) tsquery — النتيجة المعالجة مسبقًا للاستعلام

tsquery هو تركيبة من المفردات والعوامل المنطقية.

العامل المعنى مثال
& AND red & shoe
| OR red | blue
! NOT !broken
<-> يتبعه (متجاور) red <-> shoe
`<N>` مسافة N red <2> shoe

(3) ▶ مثال

SQL
SELECT to_tsquery('english', 'red & shoe');
-- 'red' & 'shoe'

SELECT to_tsquery('english', 'red | blue');
-- 'red' | 'blue'

Output:

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

5. المفهوم: دوال استعلام البحث النصي الكامل

(1) خمس دوال لبناء الاستعلام

الدالة إصدار PG تنسيق الإدخال الوصف
to_tsquery(config, text) الكل red & shoe تكتب العوامل يدويًا
plainto_tsquery(config, text) الكل red shoes نص عادي، ربط تلقائي بـ &
phraseto_tsquery(config, text) 9.6+ red shoes نص عادي، ربط تلقائي بـ <->
websearch_to_tsquery(config, text) 11+ "red shoes" -broken صيغة محرك بحث ويب
tsvector @@ tsquery الكل عامل المطابقة

(4) ▶ مثال

SQL
SELECT plainto_tsquery('english', 'red shoes');
-- 'red' & 'shoe'  (يطابق المستندات التي تحتوي على كلا المصطلحين)

Output:

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

(5) ▶ مثال

SQL
SELECT phraseto_tsquery('english', 'red shoes');
-- 'red' <-> 'shoe'  (يطابق المستندات حيث 'red' قبل 'shoe' مباشرة)

Output:

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

(6) ▶ مثال

SQL
SELECT websearch_to_tsquery('english', '"red shoes" -broken OR new');
-- 'red' <-> 'shoe' & !'broken' | 'new'

Output:

TEXT 📖 للعرض فقط
 id | name     | value 
----+----------+-------
  1 | example  | 42
(1 row)
صيغة websearch المعنى
"exact phrase" مطابقة عبارة (<->)
-word استبعاد (!)
word1 OR word2 OR (|)
word1 word2 AND (&)

(7) ▶ مثال

SQL
CREATE TABLE catalog (
  product_id SERIAL PRIMARY KEY,
  name TEXT NOT NULL,
  description TEXT,
  search_vector tsvector
);

INSERT INTO catalog (name, description, search_vector) VALUES
  ('Red Running Shoes', 'Lightweight red shoes for running', 
   to_tsvector('english', 'Lightweight red shoes for running')),
  ('Blue Casual Shoes', 'Comfortable blue shoes for daily wear',
   to_tsvector('english', 'Comfortable blue shoes for daily wear')),
  ('Red Dress', 'Elegant red dress for special occasions',
   to_tsvector('english', 'Elegant red dress for special occasions'));

-- البحث عن "red shoes"
SELECT name, description
FROM catalog
WHERE search_vector @@ plainto_tsquery('english', 'red shoes');

Output:

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

6. المفهوم: الترتيب والتمييز

(1) ts_rank الترتيب

ts_rank يحسب صلة الاستعلام بالمستند، مرجعًا قيمة عائمة من 0 إلى 1 — كلما زادت القيمة، زادت الصلة.

(8) ▶ مثال

SQL
SELECT name,
       ts_rank(search_vector, plainto_tsquery('english', 'red shoes')) AS rank
FROM catalog
WHERE search_vector @@ plainto_tsquery('english', 'red shoes')
ORDER BY rank DESC;

Output:

TEXT 📖 للعرض فقط
 id | name     | value 
----+----------+-------
  1 | example  | 42
(1 row)
دالة الترتيب الوصف حالة الاستخدام
ts_rank(vector, query) ترتيب معتمد على التكرار ترتيب عام
ts_rank_cd(vector, query) ترتيب كثافة التغطية ترتيب أكثر دقة للمستندات القصيرة
ts_rank(vector, query, weights) ترتيب موزون العنوان مرجح أعلى من النص

(9) ▶ مثال

SQL
ALTER TABLE catalog ADD COLUMN title_vector tsvector;

UPDATE catalog SET title_vector = to_tsvector('english', name);

-- تعيين أوزان: title=D(افتراضي), body=A(الأعلى)
SELECT name,
       ts_rank(
         setweight(title_vector, 'A') || setweight(search_vector, 'B'),
         plainto_tsquery('english', 'red shoes')
       ) AS rank
FROM catalog
WHERE setweight(title_vector, 'A') || setweight(search_vector, 'B')
      @@ plainto_tsquery('english', 'red shoes')
ORDER BY rank DESC;

Output:

TEXT 📖 للعرض فقط
UPDATE 3
حرف الوزن الوزن الافتراضي الاستخدام النموذجي
A 1.0 العنوان
B 0.4 النص
C 0.2 الملخص
D 0.1 معلومات تكميلية

(2) ts_headline تمييز

ts_headline يحدد المفردات المطابقة في النص الأصلي.

(10) ▶ مثال

SQL
SELECT name,
       ts_headline('english', description, plainto_tsquery('english', 'red shoes')) AS highlighted
FROM catalog
WHERE search_vector @@ plainto_tsquery('english', 'red shoes');
TEXT 📖 للعرض فقط
 name              | highlighted
-------------------+-------------------------------------------------
 Red Running Shoes | Lightweight <b>red</b> <b>shoes</b> for running

(11) ▶ مثال

SQL
SELECT ts_headline(
  'english',
  description,
  plainto_tsquery('english', 'red shoes'),
  'StartSel=<em>,StopSel=</em>,MaxWords=10,MinWords=5'
) AS highlighted
FROM catalog
WHERE search_vector @@ plainto_tsquery('english', 'red shoes');

Output:

TEXT 📖 للعرض فقط
 id | name     | value 
----+----------+-------
  1 | example  | 42
(1 row)
معامل التمييز الوصف الافتراضي
StartSel وسم بداية المطابقة <b>
StopSel وسم نهاية المطابقة </b>
MaxWords الحد الأقصى▶�لكلمات في جزء التمييز 35
MinWords الحد الأدنى للكلمات في جزء التمييز 15
ShortWord الحد الأدنى لطول الكلمة للتجاهل 3

7. المفهوم: التسريع بفهارس GIN

(1) ثلاث طرق لفهرس GIN

الطريقة الصيغة عبء التحديث الأفضل لـ
معتمد على العمود gin(to_tsvector(config, column)) إعادة بناء عند التحديث الأفضل عندما لا تتغير قيم العمود
معتمد على عمود مخزن gin(search_vector) عبء تحديث أقل تحديثات عالية التردد، يحتاج مزامنة مشغل
FASTUPDATE مفعل افتراضيًا دمج مؤجل سيناريوهات كثيفة الكتابة والقراءة

(12) ▶ مثال

SQL
CREATE INDEX idx_catalog_desc_fts
ON catalog USING gin (to_tsvector('english', description));

-- يجب أن يستخدم الاستعلام نفس التكوين
SELECT name FROM catalog
WHERE to_tsvector('english', description) @@ plainto_tsquery('english', 'red shoes');

Output:

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

(13) ▶ مثال

SQL
CREATE INDEX idx_catalog_search_fts ON catalog USING gin (search_vector);

-- الاستعلام يستخدم العمود المخزن
SELECT name FROM catalog
WHERE search_vector @@ plainto_tsquery('english', 'red shoes');

Output:

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

(14) ▶ مثال

SQL
CREATE FUNCTION catalog_search_vector_update() RETURNS trigger AS $$
BEGIN
  NEW.search_vector :=
    setweight(to_tsvector('english', COALESCE(NEW.name, '')), 'A') ||
    setweight(to_tsvector('english', COALESCE(NEW.description, '')), 'B');
  RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trg_catalog_search
BEFORE INSERT OR UPDATE OF name, description ON catalog
FOR EACH ROW EXECUTE FUNCTION catalog_search_vector_update();

Output:

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

8. المفهوم: التكوين متعدد اللغات

(1) التكوينات المدمجة في PostgreSQL

التكوين اللغة الوصف
english الإنجليزية افتراضي؛ يدعم التجذيع وكلمات التوقف
simple بدون معالجة لغوية تجزئة فقط؛ لا كلمات توقف، لا تجذيع
zhpinyin الصينية بنظام بينيين إضافة PG؛ بحث صيني إلى بينيين

(15) ▶ مثال

SQL
SELECT to_tsvector('simple', 'The red shoes');
-- 'red':2 'shoes':3 'the':1  (يحتفظ بـ 'the'، لا تجذيع)

Output:

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

(16) ▶ مثال

التكوين to_tsvector('the red shoes are nice') الخاصية
english 'nice':5 'red':2 'shoe':3 إسقاط كلمات التوقف + تجذيع
simple 'are':4 'nice':5 'red':2 'shoes':3 'the':1 أحرف صغيرة + تجزئة فقط

(2) خيارات البحث النصي الكامل الصيني

لا يأتي PostgreSQL بمحلل صيني أصلي. الطرق الشائعة:

الطريقة الإضافة التجزئة تعقيد التثبيت
zhparser إضافة PostgreSQL معتمدة على SimpleChineseSeg متوسط
pg_jieba إضافة PostgreSQL معتمدة على محلل jieba متوسط
zhpinyin إضافة PostgreSQL بحث بينيين منخفض
تجزئة طبقة التطبيق لا شيء التطبيق يولد tsvector منخفض

(17) ▶ مثال

SQL
-- تجزئة النص الصيني مسبقًا بمسافات في طبقة التطبيق
INSERT INTO catalog (name, description, search_vector)
VALUES (
  'Red Running Shoes',
  'light red running shoes for jogging',
  to_tsvector('simple', 'light red running shoes for jogging')
);

-- بحث باستعلام مجزأ
SELECT name FROM catalog
WHERE search_vector @@ to_tsquery('simple', 'red & shoes');

Output:

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

(18) ▶ مثال

SQL
CREATE TABLE articles (
  article_id SERIAL PRIMARY KEY,
  title TEXT NOT NULL,
  content TEXT,
  fts_en tsvector,
  fts_zh tsvector
);

-- تكوين english للمحتوى الإنجليزي، simple للصيني
UPDATE articles SET
  fts_en = to_tsvector('english', COALESCE(title, '') || ' ' || COALESCE(content, '')),
  fts_zh = to_tsvector('simple', COALESCE(title, '') || ' ' || COALESCE(content, ''));

-- بحث بالإنجليزية
SELECT title FROM articles
WHERE fts_en @@ plainto_tsquery('english', 'database optimization');

-- بحث بالصينية (مجزأة في التطبيق)
SELECT title FROM articles
WHERE fts_zh @@ to_tsquery('simple', 'data & optimization');

Output:

TEXT 📖 للعرض فقط
UPDATE 3

9. المفهوم: الإحصائيات وفهارس النظام

(1) ts_stat لتردد الكلمات

يساعد ts_stat في تحليل توزيع المفردات في tsvector — يستخدم عادة لضبط تكوين البحث.

(19) ▶ مثال

SQL
SELECT word, ndoc, nentry
FROM ts_stat(
  'SELECT search_vector FROM catalog'
)
ORDER BY nentry DESC
LIMIT 10;

Output:

TEXT 📖 للعرض فقط
 id | name     | value 
----+----------+-------
  1 | example  | 42
(1 row)
العمود الوصف
word المفردة
ndoc في كم مستند تظهر
nentry العدد الإجمالي للظهور

(2) فهارس النظام

(20) ▶ مثال

SQL
SELECT cfgname FROM pg_ts_config;

Output:

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

(21) ▶ مثال

SQL
-- إنشاء بيانات اختبار
INSERT INTO catalog (name, description, search_vector)
SELECT
  'Product ' || i,
  'High quality product number ' || i || ' with great features and amazing design',
  to_tsvector('english', 'High quality product number ' || i || ' with great features and amazing design')
FROM generate_series(1, 100000) AS i;

-- استعلام LIKE (بطيء، لا دعم فهرس لحرف البدل البادئ)
EXPLAIN ANALYZE
SELECT name FROM catalog WHERE description LIKE '%great features%';

-- بحث نصي كامل مع فهرس GIN (سريع)
CREATE INDEX idx_catalog_fts ON catalog USING gin (search_vector);
EXPLAIN ANALYZE
SELECT name FROM catalog
WHERE search_vector @@ plainto_tsquery('english', 'great features');

Output:

TEXT 📖 للعرض فقط
INSERT 0 1
طريقة الاستعلام الوقت دعم الفهرس القدرة
LIKE '%keyword%' ~3000ms لا شيء مطابقة سلسلة جزئية دقيقة
LIKE 'keyword%' ~5ms B-tree مطابقة بادئة
@@ بحث نصي كامل + GIN ~30ms GIN مطابقة مفردة + تجذيع + ترتيب

10. تطبيق عملي: محرك بحث منتجات التجارة الإلكترونية

يحتاج Charlie لتطبيق بحث منتجات كامل لمنصة التجارة الإلكترونية، يشمل بحث متعدد الحقول وترتيب وتمييز وتصف��ح.

SQL
-- الخطوة 1: إنشاء جدول منتجات مع tsvector محسوب مسبقًا
CREATE TABLE shop_products (
  product_id SERIAL PRIMARY KEY,
  name TEXT NOT NULL,
  category TEXT NOT NULL,
  description TEXT,
  price NUMERIC(10,2) NOT NULL,
  search_doc tsvector GENERATED ALWAYS AS (
    setweight(to_tsvector('english', COALESCE(name, '')), 'A') ||
    setweight(to_tsvector('english', COALESCE(category, '')), 'B') ||
    setweight(to_tsvector('english', COALESCE(description, '')), 'C')
  ) STORED
);

-- الخطوة 2: إدراج بيانات نموذجية
INSERT INTO shop_products (name, category, description, price) VALUES
  ('Red Running Shoes', 'Shoes', 'Lightweight red running shoes with cushioned sole', 89.99),
  ('Blue Casual Shoes', 'Shoes', 'Comfortable blue shoes for everyday casual wear', 59.99),
  ('Red Leather Dress Shoes', 'Shoes', 'Premium red leather dress shoes for formal events', 149.99),
  ('Red Silk Dress', 'Dresses', 'Elegant red silk dress for special occasions', 199.99),
  ('Running Watch', 'Accessories', 'GPS running watch with heart rate monitor', 249.99),
  ('Running Shorts', 'Clothing', 'Breathable running shorts with pockets', 39.99);

-- الخطوة 3: إنشاء فهرس GIN على العمود المولد
CREATE INDEX idx_shop_products_search ON shop_products USING gin (search_doc);

-- الخطوة 4: دالة بحث مع ترتيب وتمييز وتصفح
CREATE FUNCTION search_products(
  p_query TEXT,
  p_limit INT DEFAULT 20,
  p_offset INT DEFAULT 0
) RETURNS TABLE (
  product_id INT,
  name TEXT,
  category TEXT,
  price NUMERIC,
  rank REAL,
  headline TEXT
) AS $$
BEGIN
  RETURN QUERY
  SELECT
    s.product_id,
    s.name,
    s.category,
    s.price,
    ts_rank(s.search_doc, websearch_to_tsquery('english', p_query)) AS rank,
    ts_headline(
      'english',
      COALESCE(s.name, '') || '. ' || COALESCE(s.description, ''),
      websearch_to_tsquery('english', p_query),
      'StartSel=<mark>,StopSel=</mark>,MaxWords=20,MinWords=5'
    ) AS headline
  FROM shop_products s
  WHERE s.search_doc @@ websearch_to_tsquery('english', p_query)
  ORDER BY rank DESC
  LIMIT p_limit
  OFFSET p_offset;
END;
$$ LANGUAGE plpgsql;

-- الخطوة 5: اختبار البحث
SELECT * FROM search_products('red shoes');
SELECT * FROM search_products('"running shoes"');
SELECT * FROM search_products('running -shoes');

-- الخطوة 6: بحث مع تصفية الفئة
SELECT name, price,
       ts_rank(search_doc, plainto_tsquery('english', 'red')) AS rank
FROM shop_products
WHERE search_doc @@ plainto_tsquery('english', 'red')
  AND category = 'Shoes'
ORDER BY rank DESC;

❓ أسئلة شائعة

س ما الفرق الجوهري بين البحث النصي الكامل و LIKE؟
ج LIKE هو مطابقة سلسلة جزئية دقيقة — لا يفهم الدلالات. البحث النصي الكامل يجزئ ويزيل كلمات التوقف ويجذع، مطابقًا بالمفردة مع تركيبات منطقية وترتيب. البحث النصي الكامل لديه دعم فهرس GIN؛ LIKE بحرف بدل بادئ لا يمكنه استخدام فهرس.
س أيهما أستخدم، to_tsquery أم plainto_tsquery؟
ج إذا كان المستخدمون يكتبون مصطلحات البحث مباشرة، استخدم plainto_tsquery أو websearch_to_tsquery (PG 11+)، اللذين يحولان النص العادي إلى استعلام تلقائيًا. to_tsquery يحتاج منك كتابة عوامل & | ! يدويًا — أفضل للاستعلامات المولدة برمجيًا.
س لماذا يجب استخدام نفس التكوين للفهرس والاستعلام؟
ج التكوينات المختلفة تجزئ وتجذع بشكل مختلف. إذا استخدم الفهرس تكوين english لكن الاستعلام استخدم simple، لن تتطابق المفردات ولن يرجع البحث شيئًا. يجب أن يبقيا متسقين.
س أيهما أفضل، أعمدة GENERATED ALWAYS AS ... STORED أم المشغلات؟
ج الأعمدة GENERATED (PG 12+) تحسب تلقائيًا — لا حاجة لمشغل — وهي موصى بها. إذا كنت على PG أقل من 12 أو تحتاج دمج tsvectors عبر الجداول، استخدم مشغلًا.
س ما فائدة معلومات الموضع في tsvector؟
ج معلومات الموضع تستخدم للبحث بالعبارة (phraseto_tsquery يستخدم عامل <-> لمطابقة المفردات المتجاورة) والبحث بالتقارب (عامل المسافة `<N>`). إذا لم تكن بحاجة للبحث بالعبارة، يمكنك إسقاط المواضع في to_tsvector.
س هل يؤثر فهرس GIN على أداء الكتابة؟
ج نعم. تحديثات فهرس GIN أبطأ من B-tree لأنه يجب دمج القوائم المقلوبة. PostgreSQL يمكّن FASTUPDATE افتراضيًا، مخزنًا التحديثات ثم دمجها دفعات — مقايضة القليل من زمن استجابة الاستعلام الفوري بأداء كتابة أفضل.
س كيف أقوم بالبحث النصي الكامل الصيني؟
ج PostgreSQL لا يدعم تجزئة النص الصيني أصليًا. موصى به: تثبيت إضافة zhparser/pg_jieba (محلل أصلي)، أو تجزئة النص مسبقًا بمحلل في طبقة التطبيق وتخزينه بتكوين simple في tsvector. طريقة طبقة التطبيق هي الأبسط — لا حاجة لتثبيت إضافة.

📖 ملخص


📝 تمارين

  1. ⭐ أنشئ جدول blog_posts مع title (TEXT) و body (TEXT)، أدرج 3 صفوف، واستخدم to_tsvector + plainto_tsquery للبحث عن مقالات تحتوي على "قاعدة بيانات".

  2. ⭐⭐ أضف عمود search_vector (tsvector) إلى blog_posts، أنشئ فهرس GIN، واكتب مشغلًا يحدث search_vector تلقائيًا عند INSERT/UPDATE (وزن العنوان A، وزن النص B). ثم ابحث عن "performance optimization" مع ts_rank + ts_headline ورتب حسب المرتبة.

  3. ⭐⭐⭐ صمم جدول knowledge_articles يدعم بحثًا مختلطًا صيني-إنجليزي: المحتوى الإنجليزي بتكوين english، المحتوى الصيني بتكوين simple (تجزئة مسبقة في طبقة التطبيق)، مع عمودي tsvector وفهارس GIN مقابلة. اكتب دالة بحث تختار العمود المناسب بناءً على لغة استعلام الإدخال، تميز النتائج بـ ts_headline، ترتب بـ ts_rank، وتدعم التصفح.

Web-Tutorial.com

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

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

100%