PostgreSQL: محرك البحث النصي الكامل في PostgreSQL
آخر تحديث: 2026-08-26
1. ما ستتعلمه
- فهم المفاهيم الأساسية للبحث النصي الكامل (المستند، المفردة، القاموس، التكوين)
- استخدام نوعي البيانات
tsvectorوtsquery - استخدام
to_tsvector/to_tsquery/plainto_tsquery/phraseto_tsquery/websearch_to_tsqueryللتحويل - استخدام العامل
@@لإجراء البحث النصي الكامل - استخدام
ts_rankللترتيب وts_headlineللتمييز - إنشاء فهارس GIN لتسريع البحث النصي الكامل
- تكوين بحث نصي كامل متعدد اللغات (english/simple/zhpinyin)
- استخدام إحصائيات
ts_statواستعلامات فهرس النظام
2. القصة
منصة التجارة الإلكترونية لـ Charlie تحتوي على 500,000 سجل منتج، ويحتاج المستخدمون للبحث عن المنتجات. بدأ باستخدام LIKE '%red shoes%'، لكن البحث عن "red shoes" أرجع فقط المنتجات التي يحتوي وصفها بالصدفة على السلسلة المتتالية الدقيقة "red shoes"، واستغرق الاستعلام 3 ثوانٍ. بعد التبديل إلى البحث النصي الكامل في PostgreSQL:
- "red shoes" يطابق المنتجات التي تحتوي على كل من "red" و "shoes" (بأي ترتيب)
- كلمات التوقف يتم تجاهلها تلقائيًا (the/a/an)
- التجذيع التلقائي (shoes → shoe)
- بتسريع فهرس GIN، يستغرق الاستعلام 30 مللي ثانية فقط — أسرع 100 مرة
3. المفهوم: المفاهيم الأساسية للبحث النصي الكامل
(1) أربعة مفاهيم أساسية
| المفهوم | الوصف | مكافئ PostgreSQL |
|---|---|---|
| المستند | المحتوى النصي المراد البحث فيه | tsvector |
| الاستعلام | مصطلحات بحث المستخدم | tsquery |
| القاموس | يعرف كيفية معالجة الكلمات (كلمات التوقف، التجذيع) | فهرس نظام pg_ts_dict |
| التكوين | يجمع المحلل والقواميس | فهرس نظام pg_ts_config |
(2) خط أنابيب معالجة البحث النصي الكامل
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) ▶ مثال
SELECT * FROM ts_debug('english', 'The red shoes are beautiful');
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) ▶ مثال
SELECT to_tsvector('english', 'The red shoes are beautiful shoes');
'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) ▶ مثال
SELECT to_tsquery('english', 'red & shoe');
-- 'red' & 'shoe'
SELECT to_tsquery('english', 'red | blue');
-- 'red' | 'blue'
Output:
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) ▶ مثال
SELECT plainto_tsquery('english', 'red shoes');
-- 'red' & 'shoe' (يطابق المستندات التي تحتوي على كلا المصطلحين)
Output:
id | name | value
----+----------+-------
1 | example | 42
(1 row)
(5) ▶ مثال
SELECT phraseto_tsquery('english', 'red shoes');
-- 'red' <-> 'shoe' (يطابق المستندات حيث 'red' قبل 'shoe' مباشرة)
Output:
id | name | value
----+----------+-------
1 | example | 42
(1 row)
(6) ▶ مثال
SELECT websearch_to_tsquery('english', '"red shoes" -broken OR new');
-- 'red' <-> 'shoe' & !'broken' | 'new'
Output:
id | name | value
----+----------+-------
1 | example | 42
(1 row)
| صيغة websearch | المعنى |
|---|---|
"exact phrase" |
مطابقة عبارة (<->) |
-word |
استبعاد (!) |
word1 OR word2 |
OR (|) |
word1 word2 |
AND (&) |
(7) ▶ مثال
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:
INSERT 0 1
6. المفهوم: الترتيب والتمييز
(1) ts_rank الترتيب
ts_rank يحسب صلة الاستعلام بالمستند، مرجعًا قيمة عائمة من 0 إلى 1 — كلما زادت القيمة، زادت الصلة.
(8) ▶ مثال
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:
id | name | value
----+----------+-------
1 | example | 42
(1 row)
| دالة الترتيب | الوصف | حالة الاستخدام |
|---|---|---|
ts_rank(vector, query) |
ترتيب معتمد على التكرار | ترتيب عام |
ts_rank_cd(vector, query) |
ترتيب كثافة التغطية | ترتيب أكثر دقة للمستندات القصيرة |
ts_rank(vector, query, weights) |
ترتيب موزون | العنوان مرجح أعلى من النص |
(9) ▶ مثال
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:
UPDATE 3
| حرف الوزن | الوزن الافتراضي | الاستخدام النموذجي |
|---|---|---|
| A | 1.0 | العنوان |
| B | 0.4 | النص |
| C | 0.2 | الملخص |
| D | 0.1 | معلومات تكميلية |
(2) ts_headline تمييز
ts_headline يحدد المفردات المطابقة في النص الأصلي.
(10) ▶ مثال
SELECT name,
ts_headline('english', description, plainto_tsquery('english', 'red shoes')) AS highlighted
FROM catalog
WHERE search_vector @@ plainto_tsquery('english', 'red shoes');
name | highlighted
-------------------+-------------------------------------------------
Red Running Shoes | Lightweight <b>red</b> <b>shoes</b> for running
(11) ▶ مثال
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:
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) ▶ مثال
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:
CREATE TABLE
(13) ▶ مثال
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:
CREATE TABLE
(14) ▶ مثال
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:
INSERT 0 1
8. المفهوم: التكوين متعدد اللغات
(1) التكوينات المدمجة في PostgreSQL
| التكوين | اللغة | الوصف |
|---|---|---|
english |
الإنجليزية | افتراضي؛ يدعم التجذيع وكلمات التوقف |
simple |
بدون معالجة لغوية | تجزئة فقط؛ لا كلمات توقف، لا تجذيع |
zhpinyin |
الصينية بنظام بينيين | إضافة PG؛ بحث صيني إلى بينيين |
(15) ▶ مثال
SELECT to_tsvector('simple', 'The red shoes');
-- 'red':2 'shoes':3 'the':1 (يحتفظ بـ 'the'، لا تجذيع)
Output:
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) ▶ مثال
-- تجزئة النص الصيني مسبقًا بمسافات في طبقة التطبيق
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:
INSERT 0 1
(18) ▶ مثال
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:
UPDATE 3
9. المفهوم: الإحصائيات وفهارس النظام
(1) ts_stat لتردد الكلمات
يساعد ts_stat في تحليل توزيع المفردات في tsvector — يستخدم عادة لضبط تكوين البحث.
(19) ▶ مثال
SELECT word, ndoc, nentry
FROM ts_stat(
'SELECT search_vector FROM catalog'
)
ORDER BY nentry DESC
LIMIT 10;
Output:
id | name | value
----+----------+-------
1 | example | 42
(1 row)
| العمود | الوصف |
|---|---|
word |
المفردة |
ndoc |
في كم مستند تظهر |
nentry |
العدد الإجمالي للظهور |
(2) فهارس النظام
(20) ▶ مثال
SELECT cfgname FROM pg_ts_config;
Output:
id | name | value
----+----------+-------
1 | example | 42
(1 row)
(21) ▶ مثال
-- إنشاء بيانات اختبار
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:
INSERT 0 1
| طريقة الاستعلام | الوقت | دعم الفهرس | القدرة |
|---|---|---|---|
LIKE '%keyword%' |
~3000ms | لا شيء | مطابقة سلسلة جزئية دقيقة |
LIKE 'keyword%' |
~5ms | B-tree | مطابقة بادئة |
@@ بحث نصي كامل + GIN |
~30ms | GIN | مطابقة مفردة + تجذيع + ترتيب |
10. تطبيق عملي: محرك بحث منتجات التجارة الإلكترونية
يحتاج Charlie لتطبيق بحث منتجات كامل لمنصة التجارة الإلكترونية، يشمل بحث متعدد الحقول وترتيب وتمييز وتصف��ح.
-- الخطوة 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;
❓ أسئلة شائعة
<-> لمطابقة المفردات المتجاورة) والبحث بالتقارب (عامل المسافة `<N>`). إذا لم تكن بحاجة للبحث بالعبارة، يمكنك إسقاط المواضع في to_tsvector.📖 ملخص
- البحث النصي الكامل يحول النص إلى tsvector عبر التجزئة وإزالة كلمات التوقف والتجذيع؛ الاستعلامات تصبح tsquery
plainto_tsqueryيناسب إدخال المستخدم؛websearch_to_tsquery(PG 11+) يدعم صيغة محرك البحث- ع��مل
@@ينفذ المطابقة؛ts_rankيرتب؛ts_headlineيميز - فهارس GIN هي مفتاح أداء البحث النصي الكامل؛ فهارس الدوال وفهارس الأعمدة المخزنة لكل منها استخدامها
- الأوزان (setweight A/B/C/D) تجعل مطابقات العنوان ترتب أعلى من النص
- متعدد اللغات يستخدم تكوينات مختلفة؛ الصينية تحتاج إضافة zhparser/pg_jieba أو تجزئة طبقة التطبيق
ts_statيحلل تردد الكلمات؛ فهارس نظامpg_ts_config/pg_ts_dictتسرد التكوينات
📝 تمارين
-
⭐ أنشئ جدول
blog_postsمعtitle(TEXT) وbody(TEXT)، أدرج 3 صفوف، واستخدمto_tsvector+plainto_tsqueryللبحث عن مقالات تحتوي على "قاعدة بيانات". -
⭐⭐ أضف عمود
search_vector(tsvector) إلىblog_posts، أنشئ فهرس GIN، واكتب مشغلًا يحدث search_vector تلقائيًا عند INSERT/UPDATE (وزن العنوان A، وزن النص B). ثم ابحث عن "performance optimization" معts_rank+ts_headlineورتب حسب المرتبة. -
⭐⭐⭐ صمم جدول
knowledge_articlesيدعم بحثًا مختلطًا صيني-إنجليزي: المحتوى الإنجليزي بتكوين english، المحتوى الصيني بتكوين simple (تجزئة مسبقة في طبقة التطبيق)، مع عمودي tsvector وفهارس GIN مقابلة. اكتب دالة بحث تختار العمود المناسب بناءً على لغة استعلام الإدخال، تميز النتائج بـts_headline، ترتب بـts_rank، وتدعم التصفح.