يا جماعة الخير، السلام عليكم ورحمة الله وبركاته. معكم أخوكم أبو عمر.
خلوني أحكيلكم قصة صارت معي قبل كم سنة. كنا شغالين على منصة تعليمية ضخمة، فيها آلاف المقالات والدورات. وكان فيها مربع بحث بسيط، المفروض إنه يساعد الطلاب يلاقوا اللي بدهم إياه. في البداية، الأمور كانت ماشية، والمستخدمين قلال. البحث كان مبني على أبسط طريقة ممكن تتخيلوها، استعلام الـ LIKE في قاعدة البيانات.
لكن مع الوقت، كبرت المنصة، وزاد المحتوى، وزاد عدد المستخدمين. وهون “ولّعت معنا”. صارت توصلنا شكاوى كل يوم: “البحث بطيء جداً!”، “كتبت ‘برمجة’ ما طلعلي مقال عنوانه ‘مبرمج’!”، “بحثي بياخد دقيقة كاملة!”. صرنا في موقف محرج، والمشكلة بتكبر زي كرة الثلج. قعدت مع حالي، وفنجان القهوة السادة جنبي، وفتحت الكود لأواجه الوحش اللي ربيناه بإيدينا: استعلام LIKE '%search_term%'.
كان هذا الاستعلام هو أصل البلاء. بطيء، غبي، وغير دقيق. في تلك اللحظة، عرفت أننا بحاجة لحل جذري، حل يليق بحجم المشروع وتوقعات المستخدمين. ومن هنا بدأت رحلتنا من جحيم الـ LIKE إلى نعيم ما يُعرف بالـ “بحث كامل النص” أو Full-Text Search (FTS). واليوم، بدي أشارككم هاي التجربة بالتفصيل.
لماذا استعلام LIKE هو كابوس الأداء والدقة؟
قبل ما نحكي عن الحل، خلينا نفهم أصل المشكلة. ليش الـ LIKE سيء لهالدرجة لما يتعلق الأمر بالبحث في النصوص؟
1. كارثة الأداء: المسح الكامل للجدول (Full Table Scan)
لما تستخدم استعلام مثل WHERE content LIKE '%كلمة%'، أنت بتجبر قاعدة البيانات تعمل إشي اسمه “مسح كامل للجدول” (Full Table Scan).
تخيل عندك مكتبة ضخمة، وبدك تدور على كتاب فيه كلمة “الزيتون”. طريقة الـ LIKE بتشبه إنك تمسك كل كتاب في المكتبة، من أوله لآخره، وتفتحه صفحة صفحة وتدور على الكلمة. عملية مرهقة وبطيئة جداً، والأهم من هيك، إنها بتتجاهل أي فهارس (Indexes) ممكن تكون عاملها على العمود، لأن الـ Wildcard أو علامة النسبة المئوية % في البداية بتمنع استخدام الفهرس.
2. غباء الدقة: بحث أعمى لا يفهم اللغة
المشكلة الثانية، واللي ما بتقل أهمية عن الأولى، هي إن الـ LIKE “أعمى”. هو ما بفهم لغة، هو بقارن حروف وبس. وهذا بيؤدي لمشاكل كثيرة:
- لا يفهم تصريفات الكلمة (Stemming): لو المستخدم بحث عن “مبرمج”، الـ
LIKEما رح يلاقي مقال فيه كلمة “مبرمجون” أو “برمجة” أو “يبرمج”. هو بده تطابق حرفي. - لا يتجاهل الكلمات الشائعة (Stop Words): البحث عن “مقالات عن الذكاء الاصطناعي” رح يبحث عن كلمة “عن” بنفس أهمية “الذكاء”، وهذا غير منطقي وبقلل من جودة النتائج.
- لا يوجد ترتيب حسب الصلة (Relevance Ranking): النتائج اللي بترجع ما بتكون مرتبة حسب الأهمية. أول نتيجة بتلاقيها قاعدة البيانات هي أول نتيجة بتظهرلك، حتى لو كانت كلمة البحث مذكورة مرة واحدة في آخر المقال، ومقال تاني الكلمة مذكورة فيه 20 مرة في العنوان والمقدمة.
باختصار، الـ LIKE هو مطرقة، وأنت بتحاول تستخدمها كأداة جراحية دقيقة. ما بزبط.
المنقذ: البحث كامل النص (Full-Text Search)
هنا يأتي دور البطل، الـ FTS. البحث كامل النص هو تقنية مصممة خصيصاً للتعامل مع البحث في المحتوى النصي بكفاءة وذكاء. بدل ما يبحث حرف حرف، هو “يفهم” النص ويعالجه مسبقاً.
كيف يعمل الـ FTS؟
الفكرة عبقرية وبسيطة. بدل ما نبحث في النص الأصلي كل مرة، بنبني “فهرس” خاص بالبحث. هذا الفهرس مش زي الفهرس العادي، هو أذكى بكثير. عملية بناء الفهرس بتمر بعدة مراحل:
- التقسيم (Tokenization): يتم تقسيم النص الكامل إلى كلمات فردية تسمى “Tokens”. فجملة “القدس عاصمة فلسطين” بتصير “القدس”، “عاصمة”، “فلسطين”.
- التجذير (Stemming): هاي هي الخطوة السحرية، خصوصاً للغة العربية. يتم إرجاع كل كلمة لجذرها اللغوي. فكلمات مثل “مكتبة”، “كُتب”، “كاتب”، “يكتبون” كلها ممكن ترجع لجذر واحد هو “كتب”. هيك لو المستخدم بحث عن “كتاب”، بيقدر يلاقي كل هاي النتائج.
- حذف كلمات التوقف (Stop Words): يتم حذف الكلمات الشائعة وغير المهمة (مثل “من”، “إلى”، “في”، “هو”، “هي”) من الفهرس عشان ما تأثر على البحث وتبطئه.
- بناء الفهرس المعكوس (Inverted Index): في النهاية، يتم بناء فهرس يسجل كل كلمة فريدة والمستندات (الصفوف) اللي ظهرت فيها.
لما المستخدم يبحث، قاعدة البيانات ما بتروح للنص الأصلي، بتروح مباشرة لهذا الفهرس الذكي، بتلاقي الكلمة (وجذرها)، وبترجع النتائج بسرعة فائقة. والأجمل من هيك، إنها بتقدر تحسب “درجة الصلة” (Relevance Score) لكل نتيجة، وترتبهم من الأكثر صلة للأقل.
يلا نشتغل عملي: تطبيق FTS في PostgreSQL
الحكي النظري حلو، بس خلينا نشوف الكود. رح أستخدم PostgreSQL كمثال لأنه دعمها للـ FTS، وخصوصاً للغة العربية، ممتاز جداً.
الخطوة 1: تجهيز الجدول
لنفترض عنا جدول مقالات بسيط:
CREATE TABLE articles (
id SERIAL PRIMARY KEY,
title VARCHAR(255) NOT NULL,
content TEXT NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- إضافة بعض البيانات للتجربة
INSERT INTO articles (title, content) VALUES
('مقدمة في الذكاء الاصطناعي', 'الذكاء الاصطناعي هو فرع من علوم الحاسوب. يهدف المطورون إلى بناء آلات ذكية.'),
('تعلم البرمجة بلغة بايثون', 'لغة بايثون من أسهل لغات البرمجة للمبتدئين. المبرمجون يحبونها.'),
('تاريخ مدينة القدس', 'القدس مدينة مقدسة ولها تاريخ عريق في الديانات السماوية.');
الخطوة 2: إنشاء فهرس البحث كامل النص
هون السحر بيبدأ. بدنا نعمل عمود جديد من نوع tsvector عشان نخزن النص المُعالج، ونبني عليه فهرس من نوع GIN (Generalized Inverted Index) اللي هو مثالي للـ FTS.
نصيحة من أبو عمر: بدل ما تعمل عمود جديد، الأفضل في PostgreSQL إنك تعمل فهرس على “تعبير” (Expression Index). هذا بخلي قاعدة البيانات هي اللي تحدث الفهرس تلقائياً كل ما تعدل على النص الأصلي.
-- to_tsvector('arabic', content) هي الدالة اللي بتحول النص إلى صيغة مفهومة للـ FTS
-- 'arabic' تحدد أننا نستخدم قواعد اللغة العربية للتجذير وحذف كلمات التوقف
CREATE INDEX articles_content_fts_idx ON articles USING GIN (to_tsvector('arabic', content));
الخطوة 3: تنفيذ استعلام البحث الذكي
الآن، بدل استعلام الـ LIKE البطيء:
-- الطريقة القديمة والسيئة
SELECT title, content FROM articles WHERE content LIKE '%برمجة%';
رح نستخدم المعامل @@ مع الدالة to_tsquery:
-- الطريقة الجديدة والسريعة والدقيقة
-- to_tsquery('arabic', 'برمجة') بتحول كلمة البحث لنفس الصيغة المستخدمة في الفهرس
SELECT title, content FROM articles WHERE to_tsvector('arabic', content) @@ to_tsquery('arabic', 'برمجة');
هذا الاستعلام رح يرجعلك المقال اللي فيه “البرمجة” و”المبرمجون” لأنه بفهم الجذر اللغوي. السرعة رح تكون فرق السما عن الأرض!
الخطوة 4: الترتيب حسب الصلة (الأهمية)
عشان نخلي البحث احترافي أكثر، خلينا نرتب النتائج حسب الأهمية باستخدام الدالة ts_rank.
SELECT
title,
ts_rank(to_tsvector('arabic', content), to_tsquery('arabic', 'ذكاء')) AS relevance
FROM
articles
WHERE
to_tsvector('arabic', content) @@ to_tsquery('arabic', 'ذكاء')
ORDER BY
relevance DESC;
هيك، المقالات اللي بتذكر كلمة “ذكاء” بشكل متكرر وفي أماكن مهمة رح تظهر أولاً.
نصائح من خبرة أبو عمر
- اختر الأداة المناسبة: PostgreSQL و MySQL (بدءاً من إصدار 5.6) عندهم دعم جيد للـ FTS. لكن لو مشروعك ضخم جداً والبحث هو الميزة الأساسية فيه، فكر في استخدام محركات بحث متخصصة مثل Elasticsearch أو Meilisearch. هاي الأدوات بتعطيك قوة ومرونة أكبر بكثير.
- لا تنسَ العنوان: عادةً، الكلمة لما تكون في العنوان بتكون أهم. ممكن تعطي وزن أكبر للعنوان في حساب درجة الصلة.
-- إعطاء وزن أكبر للعنوان (A) مقارنة بالمحتوى (B) SELECT title, ts_rank( setweight(to_tsvector('arabic', title), 'A') || setweight(to_tsvector('arabic', content), 'B'), to_tsquery('arabic', 'بايثون') ) as rank FROM articles WHERE to_tsvector('arabic', title || ' ' || content) @@ to_tsquery('arabic', 'بايثون') ORDER BY rank DESC; - فكر في البحث التنبؤي (Autocomplete): الـ FTS ممتاز للبحث بعد ما المستخدم يضغط Enter. لكن عشان تعمل بحث تنبؤي سريع (يظهر النتائج وأنت بتكتب)، ممكن تحتاج لاستراتيجية مختلفة قليلاً، وغالباً ما يتم استخدام `LIKE ‘prefix%’` مع فهرس B-Tree، أو استخدام حلول متخصصة مثل Elasticsearch.
- اللغة العربية تحدي خاص: اللغة العربية غنية جداً بالاشتقاقات. تأكد من أن إعدادات اللغة (Configuration) في قاعدة بياناتك قوية وتستخدم خوارزميات تجذير (Stemming) مناسبة للعربية.
الخلاصة: ارتقِ ببحثك 🚀
الانتقال من LIKE إلى Full-Text Search ما كان مجرد تحسين تقني، كان نقلة نوعية في تجربة المستخدم. حولنا ميزة كانت مصدر إحباط وشكوى إلى أداة قوية وسريعة يعتمد عليها المستخدمون للعثور على ما يريدون بسهولة.
نصيحتي الأخيرة لكل مطور: لا تستهينوا بميزة البحث في تطبيقاتكم. استعلام LIKE قد يكون حلاً سريعاً في البداية، لكنه دين تقني رح تدفع ثمنه غالياً لاحقاً. استثمروا بعض الوقت في تعلم وتطبيق البحث كامل النص. صدقني، المجهود الإضافي في البداية “بيستاهل”، ورح يوفر عليك وعلى فريقك وعلى المستخدمين صداعاً كبيراً في المستقبل. وفقكم الله.