چند سال پیش، در پروژه‌ای که یک فروشگاه اینترنتی با ده‌ها هزار محصول را روی وردپرس و MariaDB اجرا می‌کردیم، همه چیز خوب بود تا روزی که گزارش فروش ماهانه از ۴۵ ثانیه عبور کرد. همان کد، روی محیط توسعه در کمتر از یک ثانیه اجرا می‌شد. تفاوت، نه در کوئری بود و نه در کد؛ تفاوت در حجم داده و رفتار دیتابیس واقعی بود. آن روز یاد گرفتم که نوشتن کوئری سریع SQL (Structured Query Language)، از جنس ترفند نیست؛ از جنس درک نحوه اجرای کوئری توسط موتور دیتابیس است. تجربه‌ام می‌گوید بیشتر کوئری‌های کند در پروژه‌های واقعی، نه به‌خاطر پیچیدگی، که به‌خاطر چند تصمیم ساده اما نادرست شکل می‌گیرند. این نوشته، همان مسیر عملی نوشتن کوئری سریع است که در پروژه‌های واقعی به‌کار می‌برم.

چرا کوئری سریع روی تست، روی سرور واقعی کند می‌شود؟

در تجربه من، سه دلیل اصلی باعث می‌شود کوئری‌ای که روی محیط توسعه سریع است، روی سرور واقعی کند شود. اول، حجم داده: یک جدول با هزار ردیف و یک جدول با ده میلیون ردیف، دو دنیای متفاوت هستند. ایندکسی که روی تست کار می‌کند، روی داده واقعی می‌تواند بی‌اثر شود. دوم، ترافیک همزمان: روی تست، شما تنها کاربر دیتابیس هستید؛ روی سرور، ده‌ها کوئری همزمان از کاربران مختلف اجرا می‌شود و منابع بین آن‌ها تقسیم می‌شود. سوم، تفاوت نسخه و پیکربندی: نسخه MySQL روی محیط توسعه و سرور می‌تواند متفاوت باشد و رفتار optimizer نیز تغییر کند.

مفهوم کلی دیتابیس و نقش آن در سرعت سایت در تأثیر دیتابیس بر سرعت سایت آمده است. یک قاعده شخصی که در پروژه‌ها به‌کارم آمده: همیشه کوئری‌ها را روی داده‌ای با مقیاس واقعی تست کنید، نه با داده‌ای که از پروژه نمونه آمده. اگر داده واقعی در دسترس نیست، حداقل یک نسخه از دیتابیس production را روی محیط استجینگ بسازید.

کوئری سریع، کوئری‌ای نیست که روی لپ‌تاپ شما در ۵۰ میلی‌ثانیه اجرا شود؛ کوئری‌ای است که روی سرور پرترافیک، با داده واقعی، در ۵۰ میلی‌ثانیه اجرا شود.

موتور دیتابیس کوئری را چطور اجرا می‌کند؟

پیش از ورود به اصول، باید بدانید موتور دیتابیس (Database Engine) کوئری را چطور اجرا می‌کند. در MySQL و MariaDB، مسیر اجرای یک کوئری از چهار گام اصلی عبور می‌کند:

  1. Parser: کوئری را به ساختار قابل فهم تبدیل می‌کند و خطاهای نگارشی را تشخیص می‌دهد.
  2. Optimizer: تصمیم می‌گیرد کوئری چطور اجرا شود: از کدام ایندکس استفاده کند، ترتیب JOIN را چطور بچیند، و چه الگوریتمی برای هر گام انتخاب کند. این گام، کلیدی‌ترین بخش برای سرعت کوئری است.
  3. Executor: پلن انتخاب‌شده توسط optimizer را اجرا می‌کند و نتایج را تولید می‌کند.
  4. Storage Engine: لایه ذخیره‌سازی (InnoDB یا MyISAM) که داده‌ها را از دیسک می‌خواند یا می‌نویسد.

تفاوت InnoDB و MyISAM در تفاوت InnoDB و MyISAM آمده است و انتخاب موتور درست، اولین تصمیم معماری دیتابیس است. فهم این چهار گام، به شما اجازه می‌دهد بفهمید کجای کوئری کند است. در تجربه من، بیشتر کوئری‌های کند در گام Optimizer به مشکل می‌خورند؛ چون ایندکس مناسب وجود ندارد یا ساختار کوئری طوری نوشته شده که optimizer نمی‌تواند از ایندکس موجود استفاده کند.

اصل اول: ایندکس درست، نیمی از سرعت

ایندکس (Index)، پرتکرارترین ابزار بهینه‌سازی کوئری است. اما در تجربه من، بیشتر ایندکس‌گذاری‌ها به‌طور نادرست انجام می‌شود. سه قاعده اساسی در ایندکس‌گذاری:

  • ایندکس روی ستون‌های WHERE، JOIN و ORDER BY: این سه محل، مهم‌ترین نقاط جستجو در دیتابیس هستند. اگر ستونی در هیچ‌کدام از این‌ها استفاده نمی‌شود، ایندکس روی آن معمولاً بی‌فایده است.
  • ترتیب ستون‌ها در ایندکس ترکیبی مهم است: ایندکس روی (a, b) برای کوئری‌هایی که شرط روی a دارند عالی است، ولی برای کوئری‌هایی که فقط شرط روی b دارند، تقریباً بی‌فایده. مفهوم ایندکس ترکیبی و ترتیب ستون در ایندکس‌گذاری در MySQL آمده است.
  • هر ایندکس، هزینه نوشتن دارد: هر ایندکس اضافه، در INSERT و UPDATE کندی ایجاد می‌کند. تجربه‌ام می‌گوید تیم‌ها معمولاً در ایندکس‌گذاری کم‌کاری می‌کنند، ولی گاهی هم برعکس، همه ستون‌ها را ایندکس می‌کنند که خودش فاجعه است.

روش عملی: قبل از افزودن هر ایندکس، با EXPLAIN بررسی کنید که پلن کوئری، از آن استفاده خواهد کرد. مفهوم ایندکس و انتخاب‌ش را در بهینه‌سازی جداول MySQL تفصیل داده‌ام و مسیر تفکیک ایندکس خوب از بی‌فایده در همان مقاله آمده است.

اصل دوم: EXPLAIN را قبل از هر بهینه‌سازی

EXPLAIN یکی از مهم‌ترین ابزارهای بهینه‌سازی کوئری است که کمتر از آن استفاده می‌شود. با اجرای EXPLAIN SELECT ...، موتور دیتابیس پلنی که برای اجرای کوئری انتخاب کرده را نشان می‌دهد. سه ستون کلیدی در خروجی EXPLAIN:

  1. type: روش دسترسی به جدول. مقادیر خوب: const، eq_ref، ref، range. مقادیر بد: ALL (اسکن کل جدول)، index (اسکن کل ایندکس).
  2. key: ایندکسی که optimizer انتخاب کرده. اگر مقدارش NULL باشد، یعنی ایندکسی استفاده نشده و کوئری روی اسکن کامل می‌رود.
  3. rows: تخمین تعداد ردیف‌هایی که باید بررسی شوند. عدد بالا در این ستون، نشانه کوئری کند است.

یک قاعده شخصی که در پروژه‌ها به‌کارم آمده: هر کوئری‌ای که در حالت عادی بیش از ۱۰۰ میلی‌ثانیه طول می‌کشد، باید با EXPLAIN بررسی شود. اگر مقدار type برابر ALL بود، تقریباً همیشه جای بهینه‌سازی وجود دارد. تحلیل کامل در بهینه‌سازی کوئری‌های وردپرس آمده و روش تفسیر خروجی EXPLAIN در مستندات رسمی MySQL توضیح داده شده است.

EXPLAIN، X-ریِ کوئری است؛ بدون آن، هر بهینه‌سازی، حدس است.

اصل سوم: فقط ستون‌های لازم را بکشید

یکی از پرتکرارترین اشتباهات، استفاده از SELECT * به‌جای فهرست صریح ستون‌ها است. سه دلیل که این کار را کند می‌کند:

  • داده بیشتری از دیسک خوانده می‌شود: حتی اگر جدول ایندکس مناسب داشته باشد، SELECT * مجبور است همه ستون‌ها را از جدول اصلی بخواند. اگر فقط سه ستون از بیست ستون لازم دارید، سیزده ستون اضافه بی‌دلیل خوانده می‌شوند.
  • ایندکس پوششی ممکن نیست: در بعضی حالات، اگر تمام ستون‌های پرس‌وجو در ایندکس باشند، دیتابیس می‌تواند مستقیم از ایندکس پاسخ دهد (Covering Index). با SELECT *، این حالت از دست می‌رود.
  • ترافیک شبکه: داده بیشتری از دیتابیس به اپلیکیشن منتقل می‌شود و در سایت‌های پربازدید، این تفاوت محسوس می‌شود.

توصیه عملی: حتی در کوئری‌های ساده، فهرست صریح ستون‌ها را بنویسید. اگر بعداً ستون جدیدی به جدول اضافه شد، کد شما خودکار تحت تأثیر قرار نمی‌گیرد. مفاهیم مدیریت ستون و ساختار در مدیریت کاربران MySQL و طراحی دیتابیس در MySQL آمده است.

اصل چهارم: شرط‌های WHERE را طوری بنویسید که ایندکس ببیند

نحوه نوشتن شرط‌های WHERE، تأثیر مستقیم روی استفاده optimizer از ایندکس دارد. پنج الگوی پرتکرار که ایندکس را بی‌اثر می‌کنند:

  1. استفاده از تابع روی ستون ایندکس‌شده: WHERE YEAR(created_at) = 2026 به‌جای WHERE created_at >= '2026-01-01' AND created_at < '2027-01-01'. در حالت اول، optimizer نمی‌تواند از ایندکس استفاده کند چون مقدار ستون در هر ردیف باید محاسبه شود.
  2. استفاده از != یا <>: معمولاً ایندکس را دور می‌زند. اگر منطق اجازه می‌دهد، از IN با مقادیر مشخص استفاده کنید.
  3. الگوی LIKE '%text': هر جستجویی که با % شروع شود، ایندکس را بی‌اثر می‌کند. جستجوی متن کامل باید از ابزارهای تخصصی مثل FULLTEXT استفاده کند.
  4. تبدیل نوع ضمنی: مقایسه رشته با عدد، به تبدیل خودکار و بی‌اثر شدن ایندکس منجر می‌شود. مثلاً WHERE user_id = '123' وقتی user_id عدد است.
  5. ترکیب شرط‌ها با OR: گاهی optimizer مجبور می‌شود اسکن کامل انجام دهد. اگر منطق اجازه می‌دهد، از UNION استفاده کنید یا شرط را طوری بنویسید که از ایندکس استفاده کند.

هر پنج الگو در تجربه‌ام بارها دیده شده‌اند. توصیه عملی: هر وقت کوئری کند دیدید، اول این پنج الگو را در WHERE بررسی کنید؛ در بیش از نیمی از موارد، مقصر یکی از همین‌هاست. مفاهیم دقیق کوئری در مستندات رسمی MySQL آمده است و در خطاهای رایج MySQL هم نشانه‌های این الگوها بررسی شده است.

اصل پنجم: JOIN را درست و کم‌هزینه بنویسید

JOIN، یکی از پرترافیک‌ترین عملگرهای SQL است. پنج قاعده عملی در نوشتن JOIN سریع:

  • ترتیب JOIN در نوشتن مهم است: در MySQL، جدولی که در ابتدا آمده و فیلتر کمتری دارد، اگر با جدول بزرگ‌تر JOIN شود، می‌تواند کوئری را کند کند. در تجربه من، همیشه کوچک‌ترین جدول (بعد از فیلتر) را ابتدا بیاورید.
  • ستون‌های JOIN باید ایندکس داشته باشند: به‌ویژه در جدول بزرگ‌تر. اگر ستون JOIN در جدول بزرگ‌تر ایندکس ندارد، optimizer مجبور به اسکن کل جدول می‌شود.
  • نوع ستون‌ها باید یکسان باشد: JOIN بین INT و VARCHAR، تبدیل نوع ضمنی ایجاد می‌کند و ایندکس را بی‌اثر.
  • INNER JOIN را به LEFT JOIN ترجیح دهید: وقتی منطق اجازه می‌دهد، INNER JOIN سبک‌تر و سریع‌تر است چون optimizer انعطاف بیشتری دارد.
  • از JOIN زیاد اجتناب کنید: اگر کوئری شما بیش از سه یا چهار JOIN دارد، احتمالاً باید کوئری را به چند کوئری کوچک‌تر تقسیم کنید یا از کش استفاده کنید.

روش تفصیلی تحلیل JOIN در مستندات رسمی MySQL آمده و تمرین‌های عملی در آموزش JOIN در MySQL ارائه شده است.

JOIN گران است؛ ولی گران‌تر از JOIN، JOIN نادرست است. تفاوت این دو، گاهی چند برابر زمان اجرا است.

اصل ششم: زیرپرس‌وجو یا JOIN؟

در انتخاب بین زیرپرس‌وجو (Subquery) و JOIN، قاعده قطعی وجود ندارد؛ ولی در تجربه من، سه اصل عملی کمک‌کننده است:

  1. در مقایسه با IN یا EXISTS، زیرپرس‌وجو اغلب سریع‌تر است: وقتی زیرپرس‌وجو نتیجه کوچکی برمی‌گرداند، optimizer می‌تواند آن را کش کند و از JOIN اجتناب کند.
  2. در محاسبات تجمعی، JOIN معمولاً بهتر است: وقتی می‌خواهید مجموع یا میانگین را بر اساس داده چند جدول حساب کنید، JOIN انتخاب طبیعی است.
  3. زیرپرس‌وجوهای همبسته (Correlated) خطرناک‌اند: زیرپرس‌وجویی که در هر ردیف جدول اصلی اجرا می‌شود، می‌تواند به‌طرز فاجعه‌باری کند باشد. در این حالت، JOIN انتخاب بسیار بهتری است.

قاعده شخصی که در پروژه‌ها به‌کارم آمده: هر زیرپرس‌وجوی همبسته را با EXPLAIN بررسی کنید. اگر دیدید که در پلن، برای هر ردیف اجرا می‌شود، به JOIN مهاجرت کنید. مفاهیم تفصیلی در راهنمای نوشتن کوئری سریع (همین مقاله) و در مستندات MySQL آمده است.

اصل هفتم: ORDER BY و LIMIT گران‌تر از آنچه به‌نظر می‌رسند

ORDER BY و LIMIT، دو ابزاری هستند که در ظاهر ساده به نظر می‌رسند، ولی در عمل می‌توانند کوئری را کند کنند. سه نکته:

  • ORDER BY روی ستون ایندکس‌نشده، دیتابیس را مجبور به مرتب‌سازی کل نتایج می‌کند: حتی اگر با LIMIT ۱۰ نتیجه بگیرید، دیتابیس باید تمام ردیف‌ها را مرتب کند تا ده‌تای اول را پیدا کند. ایندکس روی ستون ORDER BY، این کار را از بین می‌برد.
  • ORDER BY روی چند ستون، نیاز به ایندکس ترکیبی دارد: اگر ORDER BY created_at DESC, id DESC دارید، ایندکس روی (created_at, id) باید وجود داشته باشد.
  • ORDER BY RAND() فاجعه است: برای هر ردیف، دیتابیس تابع RAND() را اجرا و بعد مرتب‌سازی می‌کند. در جدول بزرگ، این کار می‌تواند ثانیه‌ها طول بکشد. برای انتخاب تصادفی، از روش‌های جایگزین مثل WHERE id >= RAND() * MAX(id) LIMIT 1 استفاده کنید.

اصل هشتم: صفحه‌بندیِ سریع به‌جای OFFSET بزرگ

صفحه‌بندی با OFFSET بزرگ، یکی از پرتکرارترین دام‌های کوئری‌های وب است. در تجربه من، کوئری‌ای مثل SELECT * FROM posts ORDER BY created_at DESC LIMIT 10 OFFSET 100000 می‌تواند ثانیه‌ها طول بکشد؛ چون دیتابیس باید ۱۰۰٬۰۱۰ ردیف را بخواند و بعد ۱۰۰٬۰۰۰ ردیف اول را دور بریزد.

راه‌حل: صفحه‌بندی مبتنی بر کلید (Keyset Pagination). به‌جای OFFSET، از شرط روی ستون مرتب‌سازی استفاده کنید:

SELECT id, title, created_at
FROM posts
WHERE created_at < '2026-01-01 10:00:00'
ORDER BY created_at DESC
LIMIT 10;

این روش، برای هر صفحه، فقط ده ردیف واقعی می‌خواند و از ایندکس استفاده می‌کند. مسئله سرعت صفحه‌بندی در سایت‌های پُرمحتوا بسیار حیاتی است؛ در وبلاگ‌هایی که هزاران نوشته دارند، این تفاوت می‌تواند بین پاسخ‌گویی در ۵۰ میلی‌ثانیه و ۵ ثانیه باشد. مفاهیم مرتبط در بهینه‌سازی کوئری‌های وردپرس آمده است.

اصل نهم: GROUP BY و توابع تجمعی

GROUP BY، COUNT، SUM و AVG، ابزارهای ضروری تحلیل داده هستند؛ ولی در حجم بالا، می‌توانند کوئری را کند کنند. سه نکته:

  • GROUP BY باید روی ستون ایندکس‌شده باشد: وقتی ستون‌های GROUP BY ایندکس دارند، دیتابیس می‌تواند گروه‌بندی را از ایندکس بخواند و از مرتب‌سازی کامل اجتناب کند.
  • COUNT(*) روی جداول بزرگ کند است: اگر فقط نیاز به تخمین دارید، از SHOW TABLE STATUS یا information_schema.TABLES استفاده کنید.
  • HAVING روی نتایج تجمعی، کوئری را کند می‌کند: اگر شرط روی ستون‌های اصلی است، از WHERE استفاده کنید نه HAVING. تفاوت این دو در مستندات رسمی MySQL آمده است.

در تجربه من، اگر گزارش‌های تجمعی بخشی از نیازهای اصلی سایت هستند، بهتر است آن‌ها را در جدول‌های جداگانه (Data Warehouse) یا کش ذخیره کنید و به‌طور دوره‌ای از دیتابیس اصلی بخوانید. مفهوم دیتابیس و کش در تأثیر دیتابیس بر سرعت سایت آمده است.

اصل دهم: تراکنش‌ها را کوتاه نگه دارید

تراکنش‌ها (Transactions)، ابزار ضروری برای حفظ سازگاری داده هستند؛ ولی در حجم بالا، تراکنش طولانی می‌تواند به قفل‌شدگی (Locking) و کندی منجر شود. سه نکته:

  • کوتاه نگه دارید: تراکنش را در همان بازه‌ای که داده‌ها را تغییر می‌دهید ببندید. تراکنش‌های طولانی، ردیف‌های زیادی را قفل می‌کنند و سایر کوئری‌ها را مسدود.
  • از قفل کردن ردیف‌های زیاد اجتناب کنید: در UPDATE یا DELETE، اگر کوئری بدون شرط دقیق اجرا شود، می‌تواند هزاران ردیف را قفل کند.
  • سطح ایزولاسیون (Isolation Level) را با احتیاط انتخاب کنید: سطح‌های بالاتر (مثل SERIALIZABLE) امنیت بالاتری دارند ولی قفل‌شدگی بیشتری ایجاد می‌کنند. سطح پیش‌فرض REPEATABLE READ در اکثر پروژه‌ها کافی است.

مفاهیم دقیق تراکنش و سطح ایزولاسیون در تراکنش‌ها در MySQL آمده است.

کش کوئری: چه زمانی جواب می‌دهد و چه زمانی نه

کش کوئری، لایه‌ای است که می‌تواند کوئری‌های تکراری را چند برابر سریع‌تر کند. ولی در تجربه من، این لایه در پروژه‌های پویا به‌سرعت بی‌اثر می‌شود. سه نکته:

  • کش کوئری MySQL در نسخه‌های ۸.۰ به بعد حذف شده است: چون در حجم بالا کارایی خوبی نداشت. بنابراین در نسخه‌های جدید، کش باید در لایه اپلیکیشن (Redis یا Memcached) باشد.
  • کش کوئری، مناسب داده‌های کم‌تغییر است: برای داده‌هایی مثل لیست دسته‌بندی یا تنظیمات سایت، کش کوئری عالی است. برای داده‌هایی مثل لیست سفارش یا سبد خرید، کش کوئری بی‌فایده است چون هر لحظه تغییر می‌کند.
  • کش کوئری، جایگزین بهینه‌سازی کوئری نیست: اگر کوئری اصلی کند است، کش فقط اثر کندی را به تأخیر می‌اندازد. اول کوئری را بهینه کنید، بعد کش را اضافه کنید.

مفهوم کش و لایه‌های آن در مقایسه افزونه‌های کش وردپرس و بهترین افزونه‌های کش وردپرس آمده است. در پروژه‌های فروشگاهی، ترکیب بهینه‌سازی کوئری و کش آبجکت (Redis) تفاوت محسوسی می‌سازد.

کش، مالیات نمی‌پردازد؛ فقط به تعویق می‌اندازد. اگر کوئری کند است، اول علت را برطرف کنید، بعد از کش استفاده کنید.

پایش کوئری‌های کند در عمل

پایش کوئری‌های کند، بخش جدایی‌ناپذیر بهینه‌سازی است. در تجربه من، سه ابزار اصلی وجود دارد:

  1. Slow Query Log: این لاگ، کوئری‌هایی که بیش از مدت مشخصی طول می‌کشند را ثبت می‌کند. در MySQL، با تنظیم slow_query_log و long_query_time فعال می‌شود. این لاگ، اولین ابزار برای یافتن کوئری‌های کند است.
  2. Performance Schema: این ابزار دقیق‌تر، اطلاعات جامع‌تری از اجرای کوئری‌ها ارائه می‌دهد. مقدار مصرف CPU و I/O هر کوئری قابل مشاهده است.
  3. ابزارهای اپلیکیشن: بعضی از فریم‌ورک‌ها (مثل Django Debug Toolbar و Query Monitor در وردپرس) کوئری‌های اجراشده در هر صفحه را فهرست می‌کنند و زمان هر کدام را نشان می‌دهند. مسیر ابزارها در بهترین ابزارهای تست سرعت سایت آمده است.

یک قاعده شخصی که در پروژه‌ها به‌کارم آمده: در هفته اول هر پروژه، slow query log را فعال کنید و روزانه نگاه کنید. حتی اگر سایت سریع است، این پایش پیش از بحران، کوئری‌های مشکل‌دار را آشکار می‌کند. بهینه‌سازی پیش از بحران، چند برابر ارزان‌تر از رفع آن در لحظه پیک است. مسیر کامل پایش در امنیت دیتابیس و تأثیر دیتابیس بر سرعت سایت آمده است.

جدول مرجع

اصلاثرمثال
ایندکس درستبسیار بالاایندکس روی ستون‌های WHERE، JOIN و ORDER BY
EXPLAIN قبل از بهینه‌سازیبالابررسی type و key در خروجی
فهرست صریح ستون‌هامتوسطبه‌جای SELECT *
شرط‌های WHERE سازگار با ایندکسبالاپرهیز از تابع روی ستون ایندکس‌شده
JOIN درستبالاترتیب درست و ایندکس روی ستون JOIN
پرهیز از زیرپرس‌وجوی همبستهبالاتبدیل به JOIN
ORDER BY روی ستون ایندکس‌شدهبالاایندکس ترکیبی روی چند ستون
صفحه‌بندی Keysetبسیار بالاجایگزین OFFSET بزرگ
GROUP BY روی ستون ایندکس‌شدهمتوسطایندکس روی ستون گروه‌بندی
تراکنش‌های کوتاهمتوسطکوتاه کردن بازه قفل

پرسش‌های کوتاه

چرا کوئری من روی تست سریع است ولی روی سرور کند؟ سه دلیل اصلی: حجم داده بیشتر، ترافیک همزمان، و تفاوت نسخه یا پیکربندی. همیشه کوئری‌ها را روی داده‌ای با مقیاس واقعی تست کنید.

آیا SELECT * همیشه بد است؟ در کوئری‌های ساده و جداول کوچک، تفاوت محسوس نیست. در جداول بزرگ و کوئری‌هایی که فقط به چند ستون نیاز دارند، تفاوت می‌تواند چند برابر باشد. توصیه من: از روز اول فهرست صریح ستون‌ها را بنویسید.

آیا ایندکس زیاد بد است؟ بله. هر ایندکس اضافه، سرعت INSERT و UPDATE را کم می‌کند و فضای دیسک مصرف می‌کند. ایندکس‌ها را با EXPLAIN و پایش دوره‌ای، محدود به ایندکس‌های مؤثر نگه دارید.

آیا کش کوئری می‌تواند جایگزین بهینه‌سازی شود؟ خیر. کش، اثر کوئری کند را به تأخیر می‌اندازد. اول کوئری را بهینه کنید، بعد کش را اضافه کنید.

از کجا شروع کنم اگر کوئری‌های سایت کند است؟ سه گام: اول، فعال کردن slow query log و یافتن کندترین کوئری‌ها؛ دوم، اجرای EXPLAIN روی همان کوئری‌ها؛ سوم، بررسی ایندکس‌های موجود و افزودن ایندکس‌های لازم. سه گام اول، در بیش از نیمی از موارد کافی است.

آیا ابزار خاصی برای بهینه‌سازی کوئری وجود دارد؟ بعضی از ابزارها مثل Percona Toolkit می‌توانند پیشنهاد ایندکس بدهند، ولی هیچ ابزاری جای تحلیل انسانی را نمی‌گیرد. سه سنجه اصلی: type، key و rows در EXPLAIN.

آیا آموزش SQL برای بهینه‌سازی لازم است؟ در سطح پایه، بله. برای بهینه‌سازی حرفه‌ای، نیاز به درک عمیق‌تر از رفتار optimizer، ایندکس‌ها و ساختار داده دارید. منابع آموزشی در آموزش MySQL از صفر آمده است.

از دید معمار داده

برای معماران داده و تیم‌هایی که با سیستم‌های پُرمعامله کار می‌کنند، بهینه‌سازی کوئری را باید در چارچوب «بودجه زمان پاسخ» دید، نه فقط در چارچوب یک کوئری مشخص. سه اصل بنیادی که در پروژه‌های سازمانی اثر مستقیم داشته‌اند. اصل اول، ترتیب لایه‌ها: ابتدا ساختار داده (نرمال‌سازی، انتخاب نوع ستون)، سپس ایندکس‌گذاری، سپس نوشتن کوئری، و در نهایت کش. اگر ترتیب را عوض کنید، بهینه‌سازی سطح کوئری روی ساختار اشتباه، فقط هزینه است. اصل دوم، پایش مستمر: کوئری کند، ماهیتاً یک پدیده در حال تغییر است؛ با رشد داده، کوئری‌ای که امروز سریع است، ماه بعد کند می‌شود. Slow query log و تحلیل دوره‌ای، پیش از بحران، ارزان‌تر از رفع بحران است. اصل سوم، آمادگی برای مهاجرت: اگر در سه سال آینده به‌سمت معماری توزیع‌شده یا داده‌های بزرگ می‌روید، از روز اول کوئری‌ها را طوری بنویسید که با معماری‌های مختلف قابل اجرا باشند. تجربه‌ام می‌گوید تیم‌هایی که این سه اصل را رعایت می‌کنند، در سه سال اول با هزینه‌ای پایین‌تر و پایداری بالاتری کار می‌کنند؛ در مقابل، تیم‌هایی که بهینه‌سازی را فقط به زمان بحران موکول می‌کنند، در پیک ترافیک اول با مشکل جدی رو‌به‌رو می‌شوند.

آنچه در این مسیر می‌ماند

نوشتن کوئری سریع SQL، مجموعه‌ای از تصمیم‌های ساده است که پشت سر هم گرفته می‌شوند: ایندکس درست، EXPLAIN قبل از بهینه‌سازی، شرط‌های WHERE سازگار با ایندکس، JOIN درست، صفحه‌بندی Keyset، تراکنش‌های کوتاه و پایش مستمر. تجربه‌ام می‌گوید اگر امروز فقط یک کار بکنید، فعال کردن slow query log و بررسی EXPLAIN سه کوئری کند سایت خود را انجام دهید. همین سه کوئری، به‌تنهایی می‌توانند الگوی اصلی کندی سایت شما را آشکار کنند. اگر تجربه‌ای از بهینه‌سازی کوئری در پروژه‌ای واقعی دارید که در منابع فارسی کمتر گفته شده، در دیدگاه‌ها بنویسید؛ همان تجربه‌ها، تصویر این حوزه را کامل‌تر می‌کنند. 🗄️