فصل ۵: ایندکس و کارایی — از B-Tree تا EXPLAIN ANALYZE

جست‌وجوی متن فارسی با FULLTEXT و ngram parser

وقتی LIKE '%...%' دیگر جواب نمی‌دهد

جست‌وجوی «فرش ابریشم کاشان» در عنوان و توضیحات ده‌ها هزار محصول با LIKE یعنی اسکن کامل و بدون رتبه‌بندی. ایندکس FULLTEXT (در InnoDB از 5.6) یک ایندکس معکوس از «کلمه ← ردیف‌ها» می‌سازد و نتیجه را بر اساس ارتباط رتبه‌بندی می‌کند.

ALTER TABLE products ADD FULLTEXT INDEX ft_title_desc (title, description);

-- حالت زبان طبیعی: رتبه‌بندی بر اساس ارتباط
SELECT sku, title, MATCH(title, description) AGAINST ('فرش ابریشم کاشان') AS score
FROM products
WHERE MATCH(title, description) AGAINST ('فرش ابریشم کاشان')
ORDER BY score DESC LIMIT 20;

-- حالت بولی: + الزامی، - ممنوع، * پیشوند، "..." عبارت دقیق
SELECT sku, title FROM products
WHERE MATCH(title, description)
      AGAINST ('+ابریشم +کاشان -ماشینی "لچک ترنج"' IN BOOLEAN MODE);

ستون‌های داخل MATCH باید دقیقاً با ستون‌های یک ایندکس FULLTEXT یکی باشند.

مشکل‌های parser پیش‌فرض با فارسی

  • کلمات را با فاصله و علائم جدا می‌کند؛ برای فارسی کار می‌کند، اما «فرش» و «فرش‌ها» دو کلمه‌ی مستقل‌اند (ریشه‌یابی ندارد).
  • innodb_ft_min_token_size پیش‌فرض 3 است؛ کلمات دوحرفی مثل «گل»، «نخ» و «قم» اصلاً ایندکس نمی‌شوند و جست‌وجویشان هیچ نتیجه‌ای نمی‌دهد.
  • فهرست stopword پیش‌فرض انگلیسی است و کلمات پرتکرار فارسی مثل «و» و «از» را نمی‌شناسد.
[mysqld]
innodb_ft_min_token_size = 2
ngram_token_size         = 2

این متغیرها فقط هنگام راه‌اندازی خوانده می‌شوند و پس از تغییرشان باید ایندکس FULLTEXT را حذف و دوباره بسازید.

ngram parser

parser داخلی ngram متن را به تکه‌های n کاراکتری پشت‌سرهم می‌شکند؛ با n=2، «کاشان» می‌شود «کا، اش، شا، ان». در نتیجه جست‌وجوی «فرش» در «فرش‌ها» و «فرشینه» هم پیدا می‌شود و به جداکننده‌ی کلمه وابسته نیست.

ALTER TABLE products ADD FULLTEXT INDEX ft_title_ngram (title) WITH PARSER ngram;

SELECT sku, title FROM products
WHERE MATCH(title) AGAINST ('ابریشم' IN BOOLEAN MODE);
روشمزیتعیب
parser پیش‌فرضایندکس کوچک، رتبه‌بندی معقولبدون ریشه‌یابی، مشکل کلمات کوتاه
ngramپیدا کردن بخشی از کلمه، مستقل از جداکنندهایندکس بزرگ، نتایج نامربوط بیشتر
Elasticsearch / Meilisearchتحلیلگر فارسی، تحمل غلط املایی، facetیک سرویس دیگر برای نگه‌داری و همگام‌سازی

نکته‌هایی که کمتر کسی می‌داند

  • برای دیدن اینکه متن واقعاً به چه توکن‌هایی شکسته شده: SET GLOBAL innodb_ft_aux_table = 'carpet_shop/products'; و سپس SELECT word FROM information_schema.innodb_ft_index_cache; — بهترین راه آزمایش رفتار با نیم‌فاصله و ی/ک.
  • قبل از ایندکس کردن، متن را نرمال کنید: ی/ک عربی، ارقام و اعراب. یک ستون search_text (که برنامه هنگام ذخیره پر می‌کند یا ستون تولیدشده‌ی STORED با REPLACE) و FULLTEXT روی آن، نتایج را بسیار بهتر می‌کند.
  • تغییرات FULLTEXT در InnoDB فقط پس از COMMIT دیده می‌شوند؛ در تستی که داخل تراکنش داده درج و بلافاصله جست‌وجو می‌کند، نتیجه خالی است.
  • قاعده‌ی معروف «کلمه‌ای که در بیش از ۵۰٪ ردیف‌ها باشد نادیده گرفته می‌شود» فقط مخصوص MyISAM است و در InnoDB وجود ندارد.
  • با حذف و ویرایش زیاد، ایندکس FULLTEXT ردیف‌های حذف‌شده را نگه می‌دارد؛ SET GLOBAL innodb_optimize_fulltext_only = ON; OPTIMIZE TABLE products; فقط ایندکس متنی را بهینه می‌کند.

برای ذخیره‌ی پیشرفت و شرکت در آزمون، وارد شوید — رایگان است.