اول پیدا کنید کدام کوئری کند است
بهینهسازی بدون اندازهگیری، حدس است. Slow query log همهی کوئریهایی را که بیش از یک آستانه طول کشیدهاند ثبت میکند.
SET PERSIST slow_query_log = ON;
SET PERSIST long_query_time = 0.5; -- نیم ثانیه؛ اعشار مجاز است
SET PERSIST log_slow_extra = ON; -- 8.0.14+: ستونهای اضافه مثل Rows_examined دقیق
SHOW VARIABLES LIKE 'slow_query_log_file';
# خلاصهسازی: کوئریهای مشابه با پارامتر متفاوت یکی میشوند
sudo mysqldumpslow -s t -t 10 /var/lib/mysql/server-slow.log
# ابزار حرفهایتر (Percona Toolkit)
pt-query-digest /var/lib/mysql/server-slow.log > digest.txt
بدون فایل لاگ هم میتوانید از performance_schema استفاده کنید که آمار همهی کوئریها را از زمان راهاندازی جمع کرده است:
SELECT query, exec_count, total_latency, avg_latency, rows_examined_avg
FROM sys.statement_analysis
ORDER BY total_latency DESC LIMIT 10;
مرتبسازی بر اساس زمان کل مهم است: کوئری ۵۰ میلیثانیهای که روزی یک میلیون بار اجرا میشود از کوئری ۱۰ ثانیهای روزی یک بار مهمتر است.
دامهایی که ایندکس را خاموش میکنند
| الگوی بد | چرا | بازنویسی |
|---|---|---|
WHERE DATE(created_at) = '2025-03-21' | تابع روی ستون؛ مقدار ایندکسشده دیگر مستقیم مقایسه نمیشود | created_at >= '2025-03-21' AND created_at < '2025-03-22' |
WHERE YEAR(created_at) = 2025 | همان | بازهی نیمهباز سال |
WHERE mobile = 9121234567 | ستون VARCHAR با عدد مقایسه میشود؛ هر ردیف به عدد تبدیل میشود | mobile = '09121234567' |
WHERE price * 1.1 > 1000000 | محاسبه روی ستون | price > 1000000 / 1.1 |
WHERE title LIKE '%کاشان%' | پیشوند نامعلوم | FULLTEXT (درس بعد) |
WHERE a = 1 OR b = 2 | دو ستون مختلف؛ گاهی index_merge، اغلب ALL | دو SELECT با UNION |
ORDER BY RAND() LIMIT 5 | کل جدول مرتب میشود | انتخاب تصادفی id در برنامه |
-- OR روی دو ستون، بازنویسی با UNION (هر بخش ایندکس خودش را دارد)
SELECT id FROM customers WHERE mobile = '09121234567'
UNION
SELECT id FROM customers WHERE national_id = '1234567890';
نکتههایی که کمتر کسی میداند
- عکس حالت تبدیل ضمنی بیخطر است: ستون عددی با رشته (
id = '42') مشکلی ندارد، چون ثابت یک بار تبدیل میشود. مشکل فقط ستون متنی با ثابت عددی است — رایج در کد PHP و پایتونی که شماره موبایل را int میکند. - JOIN بین دو ستون با collation متفاوت (مثلاً یک جدول قدیمی utf8mb3_general_ci و جدول جدید utf8mb4_0900_ai_ci) همان اثر تابع روی ستون را دارد؛ EXPLAIN با type=ALL لو میدهد.
- بهجای تغییر کوئریهای قدیمی با
DATE(col)میتوانید ایندکس تابعی((DATE(created_at)))بسازید؛ عبارت کوئری باید دقیقاً با عبارت ایندکس یکی باشد. log_queries_not_using_indexesرا همراه باlog_throttle_queries_not_using_indexesروشن کنید، وگرنه جدولهای کوچکی که همیشه اسکن کامل میشوند لاگ را پر میکنند.long_query_time = 0برای چند دقیقه در زمان اوج، تصویری کامل از بار واقعی میدهد (همهی کوئریها لاگ میشوند)؛ فقط فضای دیسک را زیر نظر داشته باشید و فوراً برگردانید.