DanLevy.net

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

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

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

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

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

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

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

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

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

خريطة أدوات البحث في 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 بتقسيم النص إلى صيغ أساسية (lexemes) ويشتقها. هذا صحيح للنصوص السردية، لكنه غالبًا غير مناسب للأسماء والمعرفات القصيرة:

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

بحث أسماء غير دقيقة

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 أو الـ trigrams هنا يضيف تعقيدًا دون تحسين الدقة — وللمعرفات الدقيقة، التطابق القريب أسوأ من عدم وجود تطابق على الإطلاق.

CREATE INDEX users_email_idx ON users (email);
-- Exact lookup: fast and unambiguous
SELECT id, name FROM users WHERE email = $1;

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


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

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

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

-- Trigram similarity on title catches typos; ts_rank handles body relevance
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;

FTS + unaccent للمحتوى الدولي:

-- Strip diacritical marks so "José" matches "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 يوسّع نهج الـ trigram إلى bigrams (شرائح من حرفين)، مما يحسّن النتائج بشكل كبير للغات 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، توصيات المحتوى ذات الصلة، الاستعلامات متعددة اللغات. تم تغطيته بعمق في بحث المتجه الدلالي والاستراتيجيات الهجينة.


جدول القرار

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

عند الشك: السلاسل القصيرة مع اختلافات إملائية → ثلاثيات الحروف. النصوص الطويلة لاستعلامات الكلمات المفتاحية → البحث النصي الكامل (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. دليل إعادة المحاولةpgvectorجيران مفهوميّون1. فشل الشبكة2. دليل إعادة المحاولةدمج RRFامنح كل نتيجة ائتمانًاحسب موضعها في كل قائمة.1 / (60 + rank)النتائج النهائيةالنتيجة العليا هي التي تتطابق فيها المصطلحات الدقيقةمع المعنى الدلالي.
يتفرّع الاستعلام إلى FTS وpgvector بالتوازي. كل منهما ينتج قائمة مرتبة خاصة به. تُحسب نقاط RRF لكل مستند بناءً على موقعه في كل قائمة وتُجمع النقاط — النتيجة تُظهر المستندات التي تتفق فيها الإشارتان.
-- البحث المختلط: FTS + pgvector مدمجان باستخدام 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) تُعد نقطة انطلاق شائعة. وزّعها إذا كان الاسترجاع منخفضًا؛ قللها للسرعة.


ما التالي

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

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