دليل البحث النصي في 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، وأي شيء يكون الجواب فيه ثنائيًا: إما يطابق أو لا يطابق. قد لا تبدو كـ “بحث”، لكنها تدخل في هذه المناقشة لأن أسوأ نمط هو استخدام بحث ضبابي أو دلالي لمشكلات لها إجابات صحيحة.
الاختيار ليس مسألة تعقٍّ. إنه مسألة مطابقة الأداة مع شكل الاستعلام.
عندما ينتصر البحث النصي الكامل
البحث في النصوص عن الكلمات المفتاحية. مشاركات المدونة، الوثائق، أوصاف المنتجات، تذاكر الدعم، المستندات القانونية. تم تصميم FTS لهذا النوع من المحتوى: استرجاع مفهرس ومُرتّب على نصوص اللغة الطبيعية.
استعلامات المستخدم القائمة على الكلمات المفتاحية. يكتب المستخدمون مصطلح بحث، يفلترون حسب الوسم، أو يتصفحون حسب كلمة مفتاحية. يتعامل FTS مع هذا القصد أصلاً دون الحاجة إلى بنية تضمين.
نتائج مرتبة دون تبعيات خارجية. فهارس FTS سريعة، حتمية، ولا تتطلب استدعاءات API. إشارة الصلة تأتي من تكرار المصطلح مضروبًا بموضعه في الحقل.
تصفية منطقية إلى جانب البحث. يتكامل FTS طبيعيًا مع منطق الاستعلام الحالي لديك:
SELECT * FROM postsWHERE 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 rankFROM posts, to_tsquery('english', 'postgres & performance') queryWHERE search_vector @@ queryORDER BY rank DESCLIMIT 10;setweight يحدد الأهمية: A (العنوان) يتفوق على B (المحتوى). هذا هو نموذج الصلة الكامل لمعظم حالات استخدام البحث في المحتوى.
ما لا يتقنه FTS جيدًا
- الأخطاء الإملائية في الاستعلامات — “javascipt” لن يطابق “javascript”
- أسماء الأشخاص، العناوين، الأسماء الخاصة التي لا تُجذّر بشكل متوقع
- البادئات/الإكمال التلقائي دون تكوين خاص
- الاستعلامات التي يصف فيها المستخدم مفهومًا بدلاً من تسميته
عندما تكون الـ Trigrams هي الفائزة (pg_trgm)
pg_trgm يغطي الفجوة المزعجة التي يتعثر فيها FTS باستمرار.
يقوم FTS بتقسيم النص إلى لكسيمات ويجذّرها. بالنسبة للنص السردي هذا صحيح. أما بالنسبة للأسماء والمعرفات القصيرة فغالبًا لا يكون كذلك:
- أسماء الأشخاص (“Dan Levy” → تجذير مختلف حسب القاموس وإعداد اللغة)
- أسماء الشركات، العناوين، عناوين المنتجات حيث تهتم الدقة الإملائية
- استعلامات بها أخطاء إملائية — “Micheal Jordan”، “Amaon”، “javascipt”
- الإكمال التلقائي / بحث بالبادئة
- مطابقة جزئية للسلسلة (“son” تطابق “Johnson”، “Anderson”)
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 scoreFROM usersWHERE name % $1 -- عامل % = عتبة التشابه (الافتراضي 0.3)ORDER BY score DESCLIMIT 10;عامل % يستخدم pg_trgm.similarity_threshold (الافتراضي 0.3، النطاق 0–1). بالنسبة للبحث عن الأسماء، 0.3–0.4 يلتقط الأخطاء الإملائية مع الحفاظ على مستوى ضجيج منخفض.
الإكمال التلقائي، البحث بالبادئة، والبحث داخل السلسلة
-- مطابقة البادئة للإكمال التلقائي. فهرس GIN للـ trigram يمكن أن يساعد،-- لكن فهرس B-tree للأنماط قد يكون أفضل للبادئات المتراصة من اليسار فقط.SELECT name FROM usersWHERE name ILIKE $1 || '%'ORDER BY nameLIMIT 10;
-- word_similarity للمطابقات الجزئية داخل سلاسل أطول-- ("Johnson" داخل "Andrew Johnson III")SELECT id, name, word_similarity($1, name) AS scoreFROM usersWHERE $1 <% nameORDER BY score DESCLIMIT 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_scoreFROM postsWHERE search_vector @@ to_tsquery('simple', $1) OR title % $1ORDER BY (ts_rank(search_vector, to_tsquery('simple', $1)) + similarity(title, $1)) DESCLIMIT 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_refreshBEFORE INSERT OR UPDATE OF title, body ON postsFOR 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_refreshBEFORE INSERT OR UPDATE OF name ON usersFOR 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, nameFROM usersWHERE name_search % unaccent($1)ORDER BY similarity(name_search, unaccent($1)) DESCLIMIT 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 matchingCREATE 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 rankFROM postsWHERE posts @@@ paradedb.fuzzy_phrase(field => 'title', value => 'postgres performnce')ORDER BY rank DESCLIMIT 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 والمسافة الكوسينية عملات مختلفة ولا يمكن جمعها حسابيًا.
-- Hybrid search: FTS + pgvector merged with RRFWITH 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_scoreFROM fts FULL JOIN vec ON fts.id = vec.idORDER BY rrf_score DESCLIMIT 10;مجموعة المرشحين المكوّنة من 60 مستندًا لكل فرع (LIMIT 60) تُعد نقطة انطلاق شائعة. قم بتوسيعها إذا كان الاسترجاع منخفضًا؛ قللها للسرعة.
ما التالي
يغطِّي بحث النص في Postgres مساحة واسعة، لكنه يواجه حدًا أقصى. عندما يصف المستخدمون ما يريدون بدلاً من تسميته — “شيء يساعدني على النوم أثناء الرحلة”، “مقالات عن تصحيح الأخطاء بثقة كمهندس جديد” — يفشل كل من البحث المعجمي والبحث الثلاثي الحروف.
هذا هو مجال تمثيلات المتجهات، البحث الدلالي، والهياكل الهجينة. تم التطرق إليه في البحث الدلالي بالمتجهات والاستراتيجيات الهجينة.