DanLevy.net

دليل البحث النصي في PostgreSQL 2026

أدوات البحث المتوفرة في قاعدة بياناتك ومتى تكون كل أداة جديرة بالاعتماد.

معظم الفرق تستخدم أداة بحث واحدة في PostgreSQL. الفرق التي تعرف الثلاثة أدوات تُقدِّم بحثًا أفضل مع تعقيد أقل — وتتفادى التحويل المكلف إلى خدمة بحث مخصصة قد لا تكون بحاجة إليها بعد.

هذا الدليل يغطي مجموعة الخيارات الأصلية في PostgreSQL: ما تقوم به كل أداة، متى تكون مناسبة، وكيفية دمجها.

الأدوات الثلاث

البحث النصي الكامل (tsvector / فهرس GIN) هو لغوي. يقوم بتقسيم النص إلى صيغ صرفية، يجذِّرها، ويطابق الاستعلامات مع الفهرس. يتحول “Running” و “runs” إلى نفس الصيغة. وكذلك “dog” و “dogs”. دالة الترتيب (ts_rank) تُعطي أولوية للوثائق التي تظهر فيها مصطلحات الاستعلام بشكل متكرر أو بارز.

المثلثات (pg_trgm) تقسم السلاسل إلى قطع متداخلة من 3 أحرف وتقيس عدد القطع المشتركة بين سلسلتين. “Dan” → " da", "dan", "an ". “Micheal” و “Michael” تشتركان في معظم المثلثات، لذا تكون التشابهة عالية. يجعل هذا pg_trgm ممتازًا للمطابقة الضبابية للأسماء، تحمل الأخطاء المطبعية، والإكمال التلقائي — المجال الذي يضعف فيه البحث النصي الكامل.

فهارس التطابق الدقيق (B‑tree، hash) تتعامل مع المفاتيح الأساسية، عناوين البريد الإلكتروني، المعرفات، SKU، وأي شيء يكون الجواب فيه ثنائيًا: إما يطابق أو لا يطابق. قد لا تبدو كـ “بحث”، لكنها تدخل في هذه المناقشة لأن أسوأ نمط هو استخدام بحث ضبابي أو دلالي لمشكلات لها إجابات صحيحة.

الاختيار ليس مسألة تعقٍّ. إنه مسألة مطابقة الأداة مع شكل الاستعلام.

خريطة أدوات البحث في Postgresمقارنة بين pg_trgm، البحث النصي الكامل، pgvector، والبحث الهجين حسب شكل الإدخال ونية الاستعلام.اختر primitive البحث حسب شكل الإدخاليمكن لنفس جدول Postgres أن يدعم الأربعة. الحيلة هي مطابقة الاستعلام مع النص.الكلمات الدقيقة مهمةالمعنى مهمنص قصير / منظمنص سردي طويل / قطعضبابيpg_trgmأسماء، عناوين، عناوين، أخطاء إملائية،إكمال تلقائي، سلاسل جزئية.تشابه إملائي: مسافة التهجئة.مشابهpgvectorعناصر ذات صلة، تذاكر مكررة،توصيات من أوصاف قصيرة.تشابه التضمين: مسافة المعنى.لغويالبحث النصي الكاملمقالات، وثائق، سجلات، محتوى دعمحيث يجب أن تظهر كلمات الاستعلام.صيغ صرفية، تجذير، ترتيب، فلاتر منطقية.هجينFTS + pgvectorوثائق تقنية وRAG حيث يسأل المستخدمونأسئلة مفهومية بالإضافة إلى رموز دقيقة.تشغيل كلاهما، دمج الترتيب بـ RRF.ابدأ بنية نية الاستعلام، ثم تحقق من شكل النص
الأربعة primitives للبحث في Postgres مُرَسَّمة حسب نية الاستعلام (دقيق مقابل دلالي) وشكل النص (منظم مقابل سردي). يمكن لنفس الجدول أن يحمل جميع الفهارس الأربعة — الاختيار يكون لكل استعلام، لا لكل جدول.

عندما ينتصر البحث النصي الكامل

البحث في النصوص عن الكلمات المفتاحية. مشاركات المدونة، الوثائق، أوصاف المنتجات، تذاكر الدعم، المستندات القانونية. تم تصميم FTS لهذا النوع من المحتوى: استرجاع مفهرس ومُرتّب على نصوص اللغة الطبيعية.

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

نتائج مرتبة دون تبعيات خارجية. فهارس FTS سريعة، حتمية، ولا تتطلب استدعاءات API. إشارة الصلة تأتي من تكرار المصطلح مضروبًا بموضعه في الحقل.

تصفية منطقية إلى جانب البحث. يتكامل FTS طبيعيًا مع منطق الاستعلام الحالي لديك:

SELECT * FROM posts
WHERE search_vector @@ to_tsquery('english', 'postgres & performance')
AND category = 'tutorial'
AND published_at > NOW() - INTERVAL '6 months';

إعداد FTS

-- العمود المُولد يحافظ على تحديث الفهرس تلقائيًا
ALTER TABLE posts ADD COLUMN search_vector tsvector
GENERATED ALWAYS AS (
setweight(to_tsvector('english', coalesce(title, '')), 'A') ||
setweight(to_tsvector('english', coalesce(body, '')), 'B')
) STORED;
CREATE INDEX posts_search_idx ON posts USING GIN (search_vector);
-- الاستعلام
SELECT title, ts_rank(search_vector, query) AS rank
FROM posts, to_tsquery('english', 'postgres & performance') query
WHERE search_vector @@ query
ORDER BY rank DESC
LIMIT 10;

setweight يحدد الأهمية: A (العنوان) يتفوق على B (المحتوى). هذا هو نموذج الصلة الكامل لمعظم حالات استخدام البحث في المحتوى.

ما لا يتقنه FTS جيدًا

عندما تكون الـ Trigrams هي الفائزة (pg_trgm)

pg_trgm يغطي الفجوة المزعجة التي يتعثر فيها FTS باستمرار.

يقوم FTS بتقسيم النص إلى لكسيمات ويجذّرها. بالنسبة للنص السردي هذا صحيح. أما بالنسبة للأسماء والمعرفات القصيرة فغالبًا لا يكون كذلك:

pg_trgm لا يعتمد على اللغة، وهو ما يهم عند التعامل مع أسماء من خلفيات لغوية متنوعة. يتطلب FTS إعداد قواميس لكل لغة.

بحث أسماء غير دقيق (Fuzzy)

CREATE EXTENSION IF NOT EXISTS pg_trgm;
CREATE INDEX users_name_trgm_idx ON users USING GIN (name gin_trgm_ops);
-- يجد "Micheal Jordan" عند البحث عن "Michael Jordan"
SELECT id, name, similarity(name, $1) AS score
FROM users
WHERE name % $1 -- عامل % = عتبة التشابه (الافتراضي 0.3)
ORDER BY score DESC
LIMIT 10;

عامل % يستخدم pg_trgm.similarity_threshold (الافتراضي 0.3، النطاق 0–1). بالنسبة للبحث عن الأسماء، 0.3–0.4 يلتقط الأخطاء الإملائية مع الحفاظ على مستوى ضجيج منخفض.

الإكمال التلقائي، البحث بالبادئة، والبحث داخل السلسلة

-- مطابقة البادئة للإكمال التلقائي. فهرس GIN للـ trigram يمكن أن يساعد،
-- لكن فهرس B-tree للأنماط قد يكون أفضل للبادئات المتراصة من اليسار فقط.
SELECT name FROM users
WHERE name ILIKE $1 || '%'
ORDER BY name
LIMIT 10;
-- word_similarity للمطابقات الجزئية داخل سلاسل أطول
-- ("Johnson" داخل "Andrew Johnson III")
SELECT id, name, word_similarity($1, name) AS score
FROM users
WHERE $1 <% name
ORDER BY score DESC
LIMIT 10;

فهرس GIN للـ trigram يكون مفيدًا خصوصًا لاستعلامات ILIKE '%pattern%' التي تبحث داخل النصوص ومطابقة الأخطاء الإملائية — وهي أنماط عادةً ما تُنفّذ كمسح كامل للجدول بدون فهرس trigram.

متى نختار pg_trgm على FTS

السيناريوالاستخدام
بحث عن اسم شخص أو شركة مع أخطاء إملائيةpg_trgm
الإكمال التلقائي / بحث بالبادئةpg_trgm (أو FTS مع استعلامات البادئة)
سلاسل قصيرة، معرفات، رموزpg_trgm
مقالات نثرية، وثائق، تذاكرFTS
رسائل سجل للكلمات المفتاحيةFTS
بحث متعدد اللغات عن الأسماءpg_trgm (غير معتمد على اللغة)

عندما ينتصر البحث الدقيق في SQL

بعض مشكلات “البحث” ليست بحثًا أصلاً.

“العثور على المستخدم بالبريد الإلكتروني dan@example.com” هو فحص مساواة. “العثور على الطلب ORD-12345” هو استعلام مفتاح أساسي. “قائمة المشاركات في فئة tutorial مرتبة حسب التاريخ” هي استعلام مُفلتر. هذه الحالات تُستَفاد من فهارس B‑tree أو hash.

استخدام FTS أو trigram هنا يضيف تعقيدًا دون تحسين الدقة — وللمعرفات الدقيقة، التطابق القريب أسوأ من عدم وجود تطابق.

CREATE INDEX users_email_idx ON users (email);
-- بحث دقيق: سريع ولا لبس فيه
SELECT id, name FROM users WHERE email = $1;

العبرة العامة: البحث التقريبي للمشكلات التي لها إجابات صحيحة هو خطأ تصنيفي. فهو يُعيد شيئًا — قد يكون خاطئًا بثقة.


دمج هذه الأدوات

هذه الأدوات تتكامل بسلاسة. لا تحتاج لاختيار أداة واحدة فقط.

البحث النصي الكامل + pg_trgm لصندوق بحث يتحمل الأخطاء الإملائية في الكلمات المفتاحية:

-- تشابه ثلاثي الحروف على العنوان يلتقط الأخطاء؛ ts_rank يتعامل مع صلة النص الكامل للجسم
SELECT id, title,
ts_rank(search_vector, to_tsquery('simple', $1)) AS fts_rank,
similarity(title, $1) AS trgm_score
FROM posts
WHERE search_vector @@ to_tsquery('simple', $1)
OR title % $1
ORDER BY (ts_rank(search_vector, to_tsquery('simple', $1)) + similarity(title, $1)) DESC
LIMIT 10;

البحث النصي الكامل + unaccent للمحتوى الدولي:

-- إزالة العلامات الدياكريتية بحيث يطابق "José" مع "Jose"
CREATE EXTENSION IF NOT EXISTS unaccent;
CREATE TEXT SEARCH CONFIGURATION public.simple_unaccent (COPY = pg_catalog.simple);
ALTER TEXT SEARCH CONFIGURATION public.simple_unaccent
ALTER MAPPING FOR hword, hword_part, word
WITH unaccent, simple;
ALTER TABLE posts ADD COLUMN search_vector tsvector;
CREATE TRIGGER posts_search_vector_refresh
BEFORE INSERT OR UPDATE OF title, body ON posts
FOR EACH ROW EXECUTE FUNCTION
tsvector_update_trigger(search_vector, 'public.simple_unaccent', title, body);

unaccent + pg_trgm للبحث عن الأسماء الدولية:

ALTER TABLE users ADD COLUMN name_search text;
CREATE FUNCTION users_name_search_refresh()
RETURNS trigger LANGUAGE plpgsql AS $$
BEGIN
NEW.name_search := unaccent(coalesce(NEW.name, ''));
RETURN NEW;
END;
$$;
CREATE TRIGGER users_name_search_refresh
BEFORE INSERT OR UPDATE OF name ON users
FOR EACH ROW EXECUTE FUNCTION users_name_search_refresh();
CREATE INDEX users_name_search_trgm_idx
ON users USING GIN (name_search gin_trgm_ops);
SELECT id, name
FROM users
WHERE name_search % unaccent($1)
ORDER BY similarity(name_search, unaccent($1)) DESC
LIMIT 10;

أمثلة المشغلات تتجنب استخدام unaccent() داخل أعمدة مُولَّدة أو تعبيرات الفهرس، حيث تهم قواعد عدم القابلية للتغيير في PostgreSQL. إذا غلفت unaccent() بدالة غير قابلة للتغيير خاصة بك، دوّن أن ذلك يُدخل مخاطر ترقية/تكوين.


امتدادات جديرة بالذكر

pg_trgm يأتي مع معظم توزيعات Postgres لكنه يتطلب تفعيلًا صريحًا. هو الأساس للمطابقة الضبابية للسلاسل في Postgres.

unaccent يزيل العلامات الدياكريتية قبل الفهرسة والاستعلام. يتناغم جيدًا مع كل من pg_trgm و FTS للمحتوى باللغات الأوروبية. يأتي مع Postgres.

pg_bigm يوسّع نهج الثلاثيات إلى ثنائيات (مقاطع من حرفين)، مما يحسّن النتائج بشكل كبير للغات CJK (الصينية، اليابانية، الكورية) حيث يتراجع أداء pg_trgm. يجب تثبيته منفصلًا؛ ليس جزءًا من الحزمة.

pg_search (من ParadeDB) يستبدل مجموعة GIN / tsvector القياسية بفهرس مبني على Tantivy يستخدم خوارزمية BM25. هذا يمنحك تقييم BM25 (غالبًا أفضل من ts_rank)، مطابقة ضبابية داخل استعلامات FTS، بحثًا موجهًا بالخصائص، وتسريعًا كبيرًا في الفهرسة على الجداول الضخمة. إنه مسار ترقية سهل عندما يبدأ FTS القياسي يُظهر حدودًا في الترتيب أو الأداء.

-- pg_search: BM25 full-text search with fuzzy matching
CREATE INDEX posts_bm25_idx ON posts
USING bm25 (id, title, body)
WITH (key_field = 'id', text_fields = '{"title": {}, "body": {}}');
-- Query with BM25 scoring + fuzzy matching (catches "javascipt")
SELECT id, title, paradedb.score(id) AS rank
FROM posts
WHERE posts @@@ paradedb.fuzzy_phrase(field => 'title', value => 'postgres performnce')
ORDER BY rank DESC
LIMIT 10;

pgvector يضيف تخزين المتجهات الكثيفة والبحث عن التشابه. هو الأداة المناسبة عندما يصف المستخدمون ما يريدون بدلاً من تسميته — البحث الدلالي، RAG، توصيات المحتوى ذات الصلة، الاستعلامات متعددة اللغات. تم تغطية ذلك بعمق في Semantic Vector Search and Hybrid Strategies.


جدول القرار

ما الذي تبحث عنهالتوصية
مقالات نصية، وثائق، تذاكرFTS
أسماء أشخاص/شركات مع أخطاء إملائيةpg_trgm
إكمال تلقائي، بحث بادئةpg_trgm
رموز قصيرة، معرفاتpg_trgm
رسائل سجل للكلمات المفتاحيةFTS
أسماء دوليةpg_trgm + unaccent
محتوى كبير، ترتيب أفضلpg_search (ParadeDB BM25)
المفاتيح الأساسية، رسائل بريد إلكتروني دقيقة، معرفاتفهرس B‑tree
تواريخ، نطاقات، قوائم مرتبةفهرس B‑tree
أذونات، فئات، فلاترشرط WHERE عادي
أسئلة، صيغ موازية، مفاهيمpgvector (انظر المقال التالي)

عند الشك: السلاسل القصيرة مع تباين إملائي → trigrams. النصوص الطويلة لاستعلامات الكلمات المفتاحية → FTS. المعرفات المهيكلة → فهارس عادية. الاستعلامات المفهومية أو اللغة الطبيعية → pgvector.


البحث الهجين: إشارتيّن، ترتيب واحد

عندما يُدخل المستخدم استعلامًا مثل "withRetry timeout errors" في مربع البحث، يحمل نيتين مختلفتين: أسماء رموز دقيقة يعرفها المستخدم (withRetry) ووصف مفهومي (timeout errors). لا يغطي أي بديل أولي كلاهما. تشغيل FTS والبحث المتجهي بالتوازي — ثم دمج قوائم الترتيب باستخدام دمج الترتيب العكسي (Reciprocal Rank Fusion) — يحقق ذلك.

يحسب RRF لكل نتيجة 1 / (60 + rank) في كل قائمة ويجمع القيم عبر القوائم. الثابت 60 يخفّف ميزة المراتب العليا، لذا يمكن لنتيجة تحتل المرتبة الثانية في كلتا القائمتين أن تتفوق على نتيجة تفوز بإحدى القوائم وتغيب عن الأخرى تمامًا. الأهم أن RRF لا يُجري متوسطًا للدرجات الخام بين الأساليب — ترتيب FTS والمسافة الكوسينية عملات مختلفة ولا يمكن جمعها حسابيًا.

البحث الهجين مع دمج الترتيب العكسييُفرّع الاستعلام إلى بحث نصي كامل وبحث متجهي، كل منهما ينتج ترتيبات، ودمج الترتيب العكسي يجمعها في قائمة نتائج واحدة.البحث الهجين هو إشارتيّن صادقتين، ثم ترتيب موحدلا تُجري متوسطًا للدرجات الخام. ترتيب FTS والمسافة الكوسينية عملات مختلفة.استعلام المستخدم”withRetrytimeout errors”FTS / BM25رموز وكلمات دقيقة1. مرجع API2. دليل Retrypgvectorجيران مفهوميّون1. فشل الشبكة2. دليل Retryدمج RRFامنح كل نتيجة ائتمانًاحسب موقعها في كل قائمة.1 / (60 + rank)النتائج النهائيةالنتيجة العليا هي حيث تتطابق المصطلحات الدقيقةمع المعنى الدلالي.
يُفرّع الاستعلام إلى FTS وpgvector بالتوازي. كل منهما ينتج قائمة مرتبة خاصة به. دمج RRF يُعطي كل مستند حسب موقعه في كل قائمة ويجمع الدرجات — النتيجة تُظهر المستندات التي تتفق فيها الإشارتان.
-- Hybrid search: FTS + pgvector merged with RRF
WITH fts AS (
SELECT id, ts_rank(search_vector, query) AS score,
ROW_NUMBER() OVER (ORDER BY ts_rank(search_vector, query) DESC) AS rank
FROM docs, to_tsquery('english', 'withRetry & timeout') query
WHERE search_vector @@ query
LIMIT 60
),
vec AS (
SELECT id,
ROW_NUMBER() OVER (ORDER BY embedding <=> $embedding) AS rank
FROM docs
ORDER BY embedding <=> $embedding
LIMIT 60
)
SELECT COALESCE(fts.id, vec.id) AS id,
(COALESCE(1.0 / (60 + fts.rank), 0) +
COALESCE(1.0 / (60 + vec.rank), 0)) AS rrf_score
FROM fts FULL JOIN vec ON fts.id = vec.id
ORDER BY rrf_score DESC
LIMIT 10;

مجموعة المرشحين المكوّنة من 60 مستندًا لكل فرع (LIMIT 60) تُعد نقطة انطلاق شائعة. قم بتوسيعها إذا كان الاسترجاع منخفضًا؛ قللها للسرعة.


ما التالي

يغطِّي بحث النص في Postgres مساحة واسعة، لكنه يواجه حدًا أقصى. عندما يصف المستخدمون ما يريدون بدلاً من تسميته — “شيء يساعدني على النوم أثناء الرحلة”، “مقالات عن تصحيح الأخطاء بثقة كمهندس جديد” — يفشل كل من البحث المعجمي والبحث الثلاثي الحروف.

هذا هو مجال تمثيلات المتجهات، البحث الدلالي، والهياكل الهجينة. تم التطرق إليه في البحث الدلالي بالمتجهات والاستراتيجيات الهجينة.