دليل البحث النصي في 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) تتعامل مع المفاتيح الأساسية، عناوين البريد الإلكتروني، المعرفات، رموز المخزون، وأي شيء تكون الإجابة فيه ثنائية: إما يطابق أو لا يطابق. هذه الفهارس لا تشعر بأنها “بحث”، لكنها تُدرج في هذا النقاش لأن أسوأ نمط هو استخدام بحث غير دقيق أو دلالي لمشكلات لها إجابات صحيحة.
الاختيار ليس مسألة تعقٍّ. إنه مسألة مطابقة الأداة مع شكل الاستعلام.
متى ينجح البحث النصي الكامل
البحث في النصوص السردية عن كلمات مفتاحية. مشاركات المدونات، الوثائق، أوصاف المنتجات، تذاكر الدعم، المستندات القانونية. صُمم 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 بتقسيم النص إلى صيغ أساسية (lexemes) ويشتقها. هذا صحيح للنصوص السردية، لكنه غالبًا غير مناسب للأسماء والمعرفات القصيرة:
- أسماء الأشخاص (“Dan Levy” → تُشتق بطرق مختلفة حسب القاموس وإعداد اللغة)
- أسماء الشركات، العناوين، عناوين المنتجات حيث يهم التهجئة الدقيقة
- الاستعلامات التي تحتوي أخطاء إملائية — “Micheal Jordan”، “Amaon”، “javascipt”
- الإكمال التلقائي / البحث بالبادئة
- مطابقة سلاسل جزئية (“son” تتطابق مع “Johnson”، “Anderson”)
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 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 أو الـ trigrams هنا يضيف تعقيدًا دون تحسين الدقة — وللمعرفات الدقيقة، التطابق القريب أسوأ من عدم وجود تطابق على الإطلاق.
CREATE INDEX users_email_idx ON users (email);
-- Exact lookup: fast and unambiguousSELECT id, name FROM users WHERE email = $1;الدرس الأوسع: البحث التقريبي للمشكلات التي لها إجابات صحيحة هو خطأ تصنيفي. فهو يُعيد شيئًا — قد يكون خاطئًا بثقة.
دمج هذه الأدوات
هذه الأدوات تتكامل بسلاسة. لا تختار أداة واحدة فقط.
FTS + pg_trgm لصندوق البحث الذي يتحمل الأخطاء الإملائية في الكلمات المفتاحية:
-- Trigram similarity on title catches typos; ts_rank handles body relevanceSELECT 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;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_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 يوسّع نهج الـ 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 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، توصيات المحتوى ذات الصلة، الاستعلامات متعددة اللغات. تم تغطيته بعمق في بحث المتجه الدلالي والاستراتيجيات الهجينة.
جدول القرار
| ما الذي تبحث عنه | التوصية |
|---|---|
| مقالات نثرية، وثائق، تذاكر | 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 + pgvector مدمجان باستخدام 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) تُعد نقطة انطلاق شائعة. وزّعها إذا كان الاسترجاع منخفضًا؛ قللها للسرعة.
ما التالي
يغطي بحث النص في PostgreSQL مساحة واسعة، لكنه له حد أقصى. عندما يصف المستخدمون ما يريدون بدلاً من تسميته — “شيء يساعدني على النوم أثناء الرحلة”، “مقالات عن تصحيح الأخطاء بثقة كمهندس جديد” — يفشل كل من البحث اللفظي والبحث الثلاثي.
هذا هو مجال تمثيلات المتجهات، البحث الدلالي، والهياكل المختلطة. تم التغطية في البحث الدلالي بالمتجهات والاستراتيجيات المختلطة.