يا الله، لا زلت أذكر ذلك اليوم قبل عدة سنوات. كنا في المراحل النهائية لإطلاق منصة تعليمية عربية، وكنا فخورين جداً بالمحتوى الضخم الذي جمعناه من مقالات ودروس. كان الفريق كله سهران الليالي، والقهوة هي الوقود الرسمي للمكتب. واحد من الشباب، كان اسمه أحمد، كان مسؤولاً عن واجهة البحث في المنصة.
في البداية، كانت الأمور “عال العال”. استخدمنا استعلام `LIKE` البسيط في SQL، مثل أي مبرمج مبتدئ، للبحث عن الكلمات في عناوين ومحتوى المقالات. كانت شغالة زي الحلاوة على قاعدة البيانات الصغيرة اللي كنا بنجرّب عليها. لكن المصيبة بدأت لما نقلنا قاعدة البيانات الحقيقية… آلاف المقالات، وملايين الكلمات.
فجأة، صارت عملية البحث تأخذ من 5 إلى 10 ثوانٍ! والمستخدمون بدؤوا يشتكون: “البحث بطيء!”، “البحث لا يعطيني نتائج دقيقة!”. كنت أرى أحمد وجهه أصفر، وسيرفر قاعدة البيانات “يصرخ” من الضغط، المعالج (CPU) يصل إلى 100% مع كل عملية بحث. كانت شغلة بتجيب الجلطة، والله. جلسنا ليلة كاملة نحاول تحسين أداء `LIKE`، أضفنا فهارس (indexes) هنا وهناك، لكن بدون فائدة. استعلام مثل LIKE '%كلمة%' هو أكبر عدو للفهرس.
في تلك الليلة، وبعد ما كدنا نستسلم ونقول “خلص، بدنا حل جذري ومكلف مثل Elasticsearch”، تذكرت شيئاً قرأت عنه زماناً اسمه “Full-Text Search”. كان يبدو وكأنه طوق النجاة. قلت لأحمد: “اسمع يا خوي، خلينا نجرب هالشغلة، ما إحنا خسرانين إشي”. ومن هنا، بدأت رحلتنا من جحيم `LIKE` إلى نعيم البحث بالنص الكامل.
لماذا كان `LIKE` كابوساً؟
قبل أن ننتقل للحل، دعونا نفهم أصل المشكلة. لماذا يعتبر استخدام معامل `LIKE` للبحث في النصوص فكرة سيئة جداً على نطاق واسع؟
مشكلة الفهرسة (The Indexing Problem)
قواعد البيانات تستخدم الفهارس (Indexes) لتسريع عمليات البحث، تخيلها كفهرس في آخر الكتاب. عندما تبحث عن كلمة، تذهب للفهرس مباشرة بدلاً من قراءة الكتاب صفحة صفحة. الفهارس التقليدية (مثل B-Tree) تعمل بكفاءة عندما تعرف بداية الكلمة التي تبحث عنها، مثل: WHERE title LIKE 'برمجة%'.
لكن الكارثة تحدث عندما تستخدم علامة النسبة المئوية % في بداية مصطلح البحث: WHERE content LIKE '%برمجة%'. هنا، أنت تقول لقاعدة البيانات: “ابحث لي عن كلمة ‘برمجة’ في أي مكان داخل النص”. قاعدة البيانات المسكينة لا تستطيع استخدام الفهرس، فتضطر إلى المرور على كل سجل في الجدول (عملية تسمى Full Table Scan)، وتقرأ كل المحتوى حرفاً حرفاً لتبحث عن الكلمة. مع نمو حجم البيانات، تصبح هذه العملية بطيئة بشكل لا يطاق.
الدقة المحدودة (Limited Accuracy)
معامل `LIKE` “غبي” نوعاً ما. هو يطابق الأنماط حرفياً ولا يفهم اللغة:
- لا يفهم تصريفات الكلمات: إذا بحث المستخدم عن “مبرمج”، فلن يجد مقالاً يحتوي على كلمة “مبرمجون” أو “برمجة”.
- لا يفهم المرادفات: البحث عن “حاسوب” لن يظهر نتائج تحتوي على “كمبيوتر”.
- لا يوجد ترتيب حسب الأهمية: كل النتائج متساوية في نظره. مقال يذكر الكلمة مرة واحدة في الهامش له نفس أهمية مقال يتمحور حولها.
دخول البطل: ما هو البحث بالنص الكامل (Full-Text Search)؟
هنا يأتي دور المنقذ. البحث بالنص الكامل (FTS) هو تقنية مدمجة في معظم أنظمة قواعد البيانات الحديثة (مثل PostgreSQL و MySQL و SQL Server) مصممة خصيصاً للبحث داخل كميات كبيرة من النصوص بذكاء وسرعة فائقة.
كيف يعمل؟ السحر وراء الكواليس
بدلاً من المرور على النص حرفاً حرفاً، يقوم FTS بعملية تحضير مسبقة للنصوص، وهذا هو سر قوته:
- التقطيع (Tokenization): يكسر النص الطويل إلى كلمات فردية تسمى “Tokens”.
- التطبيع (Normalization): يحول الكلمات إلى صيغة موحدة. مثلاً، إزالة علامات الترقيم وتحويل كل الحروف إلى حالة صغيرة (في اللغات الإنجليزية).
- كلمات التوقف (Stop Words): يتجاهل الكلمات الشائعة جداً التي لا تحمل معنى كبيراً في البحث (مثل “في”، “من”، “على”، “the”، “is”).
- التجذير (Stemming): هذه هي الخطوة السحرية! يقوم بإرجاع الكلمات إلى جذرها اللغوي. فمثلاً، الكلمات “مكتبة”، “كتاب”، “كاتب”، “مكتبات” قد ترجع جميعها إلى الجذر “كتب”. هذا الأمر حيوي جداً للغة العربية الغنية بالاشتقاقات.
بعد هذه العمليات، يقوم FTS ببناء فهرس خاص جداً يسمى “الفهرس المقلوب” (Inverted Index). هذا الفهرس هو عبارة عن قاموس يربط كل كلمة (بعد معالجتها) بقائمة من المستندات التي ظهرت فيها. عندما تبحث عن كلمة، يذهب المحرك مباشرة إلى هذا الفهرس السريع ويجلب النتائج في أجزاء من الثانية.
يلا نطبّق: أمثلة عملية مع PostgreSQL و MySQL
الكلام النظري جميل، لكن “الحكي ببلاش”. خلينا نشوف كيف نطبق هذا الكلام عملياً. سأستخدم PostgreSQL كمثال رئيسي لأنه الأقوى في هذا المجال، ثم سأذكر كيف يتم الأمر في MySQL.
البحث بالنص الكامل في PostgreSQL (My Favorite)
PostgreSQL يوفر أدوات قوية جداً للبحث بالنص الكامل، ويدعم اللغة العربية بشكل ممتاز.
-- لنفترض أن لدينا جدول مقالات بهذا الشكل
CREATE TABLE articles (
id SERIAL PRIMARY KEY,
title VARCHAR(255),
content TEXT
);
-- الخطوة 1: إضافة عمود من نوع tsvector لتخزين النص المُعالج
-- tsvector هو نوع بيانات خاص يخزن الكلمات والجذور
ALTER TABLE articles ADD COLUMN tsv tsvector;
-- الخطوة 2: إنشاء دالة trigger لتحديث هذا العمود تلقائياً عند إضافة أو تعديل مقال
-- هذا يضمن أن الفهرس دائماً محدّث
CREATE OR REPLACE FUNCTION articles_tsvector_update() RETURNS trigger AS $$
begin
-- to_tsvector هي الدالة التي تقوم بكل السحر (تقطيع، تجذير، إلخ)
-- 'arabic' تحدد أننا نستخدم قواعد اللغة العربية
new.tsv :=
to_tsvector('arabic', coalesce(new.title, '') || ' ' || coalesce(new.content, ''));
return new;
end
$$ LANGUAGE plpgsql;
CREATE TRIGGER tsvectorupdate BEFORE INSERT OR UPDATE
ON articles FOR EACH ROW EXECUTE PROCEDURE articles_tsvector_update();
-- الخطوة 3: إنشاء فهرس GIN (Generalized Inverted Index) على العمود الجديد
-- هذا هو ما يجعل البحث سريعاً جداً
CREATE INDEX articles_tsv_idx ON articles USING gin(tsv);
-- الخطوة 4: البحث!
-- to_tsquery تحول كلمة البحث إلى صيغة يفهمها الفهرس
-- لاحظ استخدام '&' للبحث عن الكلمتين معاً (AND)
SELECT title, content FROM articles
WHERE tsv @@ to_tsquery('arabic', 'برمجة&ذكاء'); -- يبحث عن المقالات التي تحتوي على "برمجة" و "ذكاء"
بهذه الخطوات البسيطة، تحول استعلامنا من ثوانٍ طويلة إلى ميلي ثانية. الفرق كان كالليل والنهار!
البحث بالنص الكامل في MySQL
MySQL أيضاً يدعم FTS، وإن كان أبسط قليلاً من PostgreSQL. الطريقة مباشرة أكثر.
-- لنفترض أن لدينا هذا الجدول
CREATE TABLE articles (
id INT UNSIGNED AUTO_INCREMENT NOT NULL PRIMARY KEY,
title VARCHAR(200),
content TEXT
) ENGINE=InnoDB;
-- الخطوة 1: إضافة فهرس FULLTEXT على الأعمدة التي نريد البحث فيها
-- يمكن عمل هذا عند إنشاء الجدول أو بعده
ALTER TABLE articles ADD FULLTEXT(title, content);
-- الخطوة 2: البحث!
-- نستخدم دالة MATCH() ... AGAINST()
SELECT title, content FROM articles
WHERE MATCH(title, content) AGAINST('برمجة' IN NATURAL LANGUAGE MODE);
-- للبحث عن عبارة محددة
SELECT title, content FROM articles
WHERE MATCH(title, content) AGAINST('"الذكاء الاصطناعي"' IN BOOLEAN MODE);
الأمر أسهل في الإعداد، لكنه يعطيك تحكماً أقل في عملية المعالجة مقارنة بـ PostgreSQL. ومع ذلك، يظل أفضل بمليون مرة من استخدام `LIKE`.
نصائح من أبو عمر (من الكيس)
بعد سنوات من التعامل مع هذه التقنية، جمعت لكم هذه النصائح العملية:
- ابدأ بالبسيط: قبل أن تفكر في حلول خارجية معقدة ومكلفة مثل Elasticsearch أو Algolia، جرب الـ Full-Text Search المدمج في قاعدة بياناتك. في 80% من الحالات، سيكون كافياً جداً وسريعاً بشكل مذهل.
- افهم لغتك: كما رأيت، حددنا اللغة ‘arabic’ في PostgreSQL. هذا مهم جداً ليعرف المحرك كيف يتعامل مع التجذير وكلمات التوقف الخاصة بالعربية. تأكد من أن إعداداتك صحيحة.
- الترتيب حسب الصلة (Relevance Ranking): من أجمل ميزات FTS أنها لا تعطيك النتائج فقط، بل يمكنها ترتيبها حسب مدى صلتها ببحثك. مقال يذكر الكلمة 5 مرات في العنوان أهم من مقال يذكرها مرة واحدة. استخدم دوال مثل
ts_rankفي PostgreSQL أو স্কোর الذي تعيده دالةMATCHفي MySQL لترتيب النتائج وتقديم الأفضل للمستخدم أولاً.- متى تتجاوز FTS؟ كن واقعياً. إذا كان مشروعك بحجم فيسبوك أو جوجل، أو كنت تحتاج إلى تحليلات نصوص معقدة جداً وتجميعات ضخمة (aggregations) على نطاق موزع، هنا قد يكون الوقت مناسباً للنظر في محركات بحث متخصصة مثل OpenSearch/Elasticsearch. لكن بالنسبة لمعظم التطبيقات، FTS هو الحل الأمثل.
الخلاصة والزبدة 🏁
يا جماعة الخير، قصة انتقالنا من `LIKE` إلى `Full-Text Search` كانت نقطة تحول في مشروعنا. لقد أنقذت أداء التطبيق، وحسنت تجربة المستخدم بشكل جذري، والأهم، أعادت النوم إلى عيون المبرمج أحمد! 😂
الدرس المستفاد هنا بسيط: لا تستخدم المطرقة لفك برغي. لكل أداة استخدامها. معامل `LIKE` له استخداماته في مطابقة الأنماط البسيطة، لكنه ليس أداة للبحث في النصوص. الـ Full-Text Search هو الأداة الصحيحة والمصممة خصيصاً لهذه المهمة، وهي موجودة تحت يديك في قاعدة بياناتك تنتظر من يكتشفها.
فيا صديقي المبرمج، إذا كنت لا تزال تستخدم LIKE '%...%' للبحث في نصوصك، أتمنى أن تكون هذه المقالة هي دعوة لك لتجربة الجديد. أعدك أنك لن تندم. شد حيلك، ولا تضلّك عالقديم!