توسعهدهندگان اغلب یک سوال مشابه از من میپرسند: «API من کند است. از کجا شروع کنم؟» واکنش معمول، ارتقای سرور یا دو برابر کردن RAM است. این کار هزینه دارد و به ندرت علت اصلی مشکل را حل میکند. در اکثر اپلیکیشنهای Laravel، گلوگاه (bottleneck) در لایه پایگاه داده قرار دارد. نحو (syntax) ظریف این فریمورک باعث میشود به راحتی فراموش کنیم که هر فراخوانی Eloquent در نهایت به SQL تبدیل میشود و اغلب دردسرها از همانجا (در SQL) شروع میشوند.
قبل از اینکه تنظیمات سرور را تغییر دهید، کوئریهای خود را به صورت روشمند بررسی کنید.
با تشخیص درست شروع کنید
در تاریکی بهینهسازی نکنید. بازنویسی تصادفی کوئریها حدس و گمان است و حدس و گمان ساعتها وقت تلف میکند.
شما باید دستوراتی را پیدا کنید که بیشترین زمان کل را مصرف میکنند. اپلیکیشن خود را تحت بار واقعی (real load) زیر نظر بگیرید. Laravel Telescope نمای شفافی از هر کوئری اجرا شده در طول یک درخواست، همراه با زمانبندی آن، به شما میدهد. Laravel Debugbar آنها را در حین توسعه محلی (local development) در مرورگر شما نمایش میدهد تا بتوانید ناهنجاریها را بلافاصله شناسایی کنید. زمانی که نیاز دارید مشکلات را در محیط عملیاتی (production) شناسایی کنید، MySQL Slow Query Log را فعال کنید. این قابلیت دستوراتی را که از آستانه (threshold) تعیینشده توسط شما فراتر میروند، ثبت میکند که آن را برای یافتن موارد غیرمنتظرهای که در مجموعهدادههای کوچک ظاهر نمیشوند، ایدهآل میکند. اگر سیستم بزرگتری دارید، یک ابزار Application Performance Monitoring (APM) میتواند نقاط انتهایی (endpoints) کند HTTP را با فراخوانیهای خاص پایگاه داده مرتبط کند.
هنگام بررسی دادهها، به دنبال دو چیز باشید: زمان اجرای مطلق و دفعات فراخوانی. کوئریای که ۴۰ میلیثانیه طول میکشد، بیخطر به نظر میرسد تا زمانی که متوجه شوید در هر دقیقه دو هزار بار اجرا میشود. یک گزارش سه ثانیهای که هر ساعت یک بار اجرا میشود، ممکن است اهمیت کمتری نسبت به یک جستجوی نیمثانیهای داشته باشد که در هر صفحه اجرا میشود. ابتدا مشکلات پربازده (high-impact) را حل کنید.
از درخواست همه چیز خودداری کنید
استفاده از SELECT * راحت است، اما هزینهبر هم هست. وقتی Model::all() را مینویسید یا یک مجموعه نتایج را بدون نام بردن ستونها واکشی میکنید، MySQL تمام فیلدها را برای هر ردیف مطابقتیافته جابهجا میکند. این شامل فیلدهای متنی بزرگ، JSON blobها و هر چیز دیگری است که در جدول قرار دارد. مجموعه نتایج بزرگتر میشود، مصرف حافظه بالا میرود و زمان صرف شده برای سریالسازی (serializing) پاسخ افزایش مییابد.
صریح باشید. اگر کنترلر شما فقط به فیلدهای id ،name و email نیاز دارد، دقیقاً همانها را درخواست کنید:
User::select('id', 'name', 'email')->get();
در Query Builder نیز همین اصل حاکم است. محمولههای (payloads) کوچکتر با سرعت بیشتری در شبکه جابهجا میشوند و RAM کمتری در سرور اپلیکیشن شما مصرف میکنند. این یکی از ارزانترین دستاوردهای ممکن است، با این حال به راحتی نادیده گرفته میشود زیرا Laravel رفتار پیشفرض را SELECT * قرار داده است.
اجازه دهید EXPLAIN تغییرات شما را هدایت کند
هرگز یک کوئری کند را بدون اجرای ابتدا EXPLAIN بازنویسی نکنید. در MySQL، کلمه کلیدی EXPLAIN طرح اجرای کوئری (execution plan) را نشان میدهد. این دستور دقیقاً نشان میدهد که بهینهساز (optimizer) چگونه قصد دارد دادههای شما را پیدا کند.
به ستون type توجه کنید. اگر ALL را دیدید، یعنی MySQL در حال انجام یک Full Table Scan است. این یعنی برای برآورده کردن شرط WHERE شما، در حال خواندن تکتک ردیفهاست. به ستون key نگاه کنید تا ببینید آیا بهینهساز اصلاً از ایندکس استفاده میکند یا خیر. سپس ستون Extra را بررسی کنید. اگر Using temporary یا Using filesort را مشاهده کردید، به این معناست که MySQL در حال ساخت جداول میانی یا مرتبسازی در حافظه است، زیرا ساختار فعلی شما نمیتواند کوئری را به شکلی بهینه پاسخ دهد.
دستور EXPLAIN را در کلاینت MySQL خود اجرا کنید یا از ابزاری استفاده کنید که خروجی را برای شما قالببندی کند. وقتی طرح را دیدید، متوجه خواهید شد که مشکل از نبود ایندکس است، یا یک Join اشتباه، و یا شرطی که موتور نمیتواند آن را بهینه کند. در این صورت دیگر نیازی به حدس زدن نیست.
با هدفمندی ایندکسگذاری کنید
ایندکسها قدرتمندترین ابزار برای سرعت بخشیدن به جستجوها هستند، اما تنها زمانی کار میکنند که با نحوه کوئری زدن شما مطابقت داشته باشند. بدون ایندکس مناسب، MySQL ردیف به ردیف اسکن میکند. این ممکن است در محیط توسعه روی جدولی با هزار ردیف خوب به نظر برسد، اما در محیط عملیاتی روی جدولی با ده میلیون ردیف، سیستم از کار بیفتد.
با ایندکسهای تکستونی برای فیلدهایی که به طور مکرر در بندهای WHERE ظاهر میشوند، شروع کنید. اگر دائماً بر اساس status فیلتر میکنید، یک ایندکس روی status اضافه کنید.
زمانی که یک کوئری چندین ستون را با هم فیلتر میکند، به سراغ ایندکسهای ترکیبی (composite indexes) بروید. ترتیب ستونها در داخل ایندکس اهمیت دارد، زیرا MySQL ایندکسهای ترکیبی را از چپ به راست میخواند. این موضوع «قاعده پیشوند سمت چپ» (leftmost prefix rule) نامیده میشود. اگر کوئری شما بر اساس user_id جستجو میکند و سپس بر اساس created_at مرتبسازی میکند، یک ایندکس ترکیبی روی (user_id, created_at) کمک شایانی میکند. اگر ترتیب را برعکس کنید، ممکن است بهینهساز اصلاً از ایندکس برای فیلتر استفاده نکند.
تمام ستونها را ایندکس نکنید. هر ایندکس باعث ایجاد سربار (overhead) در عملیات Insert، Update و Delete میشود، زیرا MySQL باید ساختار آن را حفظ کند. آنها را آگاهانه و بر اساس الگوهایی که در طول اندازهگیری یافتهاید، اضافه کنید.
توابع را از ستونها دور نگه دارید
این اشتباه بهطور بیصدا باعث غیرفعال شدن ایندکسها میشود. وقتی یک ستون را درون یک تابع در یک عبارت WHERE قرار میدهید، MySQL نمیتواند
