بهینهسازی MySQL یکی از کلیدیترین مهارتها برای هر مدیر سرور و توسعهدهندهٔ وب است، چون در بسیاری از برنامهها دیتابیس بزرگترین گلوگاه عملکرد محسوب میشود. یک کوئری بهینهنشده یا ایندکس گمشده میتواند زمان بارگذاری صفحه را از میلیثانیه به چند ثانیه برساند و در ترافیک بالا کل سرور را از پا درآورد. در این راهنمای عملی، از تنظیم پارامترهای موتور InnoDB و طراحی ایندکس گرفته تا تحلیل کوئریهای کند و نگهداری منظم، همه را با فرمانهای واقعی بررسی میکنیم.
نکتهٔ مهم این است که بهینهسازی دیتابیس فرایندی دادهمحور است، نه حدسی. ابتدا باید کوئریهای کند و گلوگاهها را شناسایی کنید، سپس تغییر هدفمند اعمال کنید و اثر آن را بسنجید. تغییر کورکورانهٔ صدها پارامتر بدون اندازهگیری، اغلب بیش از آنکه کمک کند، مشکل ایجاد میکند.
شناسایی گلوگاهها پیش از بهینهسازی MySQL
اولین قدم در بهینهسازی MySQL، فعالکردن لاگ کوئریهای کند (slow query log) است. این لاگ هر کوئری که بیش از حد مشخصی طول بکشد را ثبت میکند و به شما نشان میدهد کجا باید تمرکز کنید:
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL slow_query_log_file = '/var/log/mysql/slow.log';
SET GLOBAL long_query_time = 1;
SET GLOBAL log_queries_not_using_indexes = 'ON';
با تنظیم long_query_time روی ۱ ثانیه، هر کوئری کندتر از یک ثانیه ثبت میشود. پارامتر log_queries_not_using_indexes نیز کوئریهایی که از ایندکس استفاده نمیکنند را ثبت میکند؛ اینها اغلب مقصران اصلی هستند. پس از جمعآوری داده، با ابزار mysqldumpslow میتوانید پرتکرارترین کوئریهای کند را خلاصه کنید:
mysqldumpslow -s t -t 10 /var/log/mysql/slow.log
خواندن پلن اجرای کوئری با EXPLAIN
قدرتمندترین ابزار برای فهمیدن اینکه چرا یک کوئری کند است، دستور EXPLAIN است. این دستور نشان میدهد MySQL چطور قصد دارد کوئری را اجرا کند:
EXPLAIN SELECT * FROM orders WHERE customer_id = 4021 AND status = 'paid';
در خروجی، به ستون type و rows دقت کنید. اگر type برابر ALL باشد، یعنی MySQL کل جدول را اسکن میکند (full table scan) که برای جداول بزرگ فاجعه است. مقدار مطلوب ref یا range یا const است. ستون rows تعداد ردیفهای تخمینی بررسیشده را نشان میدهد؛ هرچه کمتر بهتر.
طراحی درست ایندکسها
ایندکسها مهمترین ابزار برای سرعتبخشیدن به کوئریها هستند. یک ایندکس مانند فهرست انتهای کتاب عمل میکند و به MySQL اجازه میدهد بدون خواندن کل جدول، مستقیم به ردیف موردنظر برسد. اما ایندکسگذاری بیش از حد هم مضر است، چون هر عملیات نوشتن باید ایندکسها را هم بهروز کند.
ایندکس روی ستونهای پرکاربرد در WHERE و JOIN
ستونهایی که مکرراً در شرطهای WHERE، JOIN و ORDER BY استفاده میشوند، بهترین کاندیدای ایندکس هستند. برای مثال:
CREATE INDEX idx_orders_customer_status ON orders (customer_id, status);
CREATE INDEX idx_users_email ON users (email);
ایندکس ترکیبی (composite) روی customer_id و status برای کوئری بالا بسیار مؤثر است. نکتهٔ کلیدی ترتیب ستونها در ایندکس ترکیبی است: ستونی که در شرط تساوی استفاده میشود باید اول بیاید. قانون «پیشوند چپ» (leftmost prefix) میگوید MySQL فقط میتواند از ابتدای ایندکس ترکیبی استفاده کند.
نکتهٔ حرفهای: از ایندکس روی ستونهایی با تنوع کم (مثل ستون جنسیت با دو مقدار) خودداری کنید. چنین ایندکسی بازده کمی دارد چون هنوز نیمی از جدول را باید بخواند. ایندکس روی ستونهایی با کاردینالیتی بالا مثل ایمیل یا شناسه مؤثرترند.
ایندکس پوششی (Covering Index)
اگر تمام ستونهای موردنیاز یک کوئری در خود ایندکس موجود باشد، MySQL اصلاً نیازی به مراجعه به جدول اصلی ندارد؛ این را ایندکس پوششی میگویند و میتواند کوئری را چند برابر سریعتر کند. برای مثال اگر فقط ستون status را میخواهید، گنجاندن آن در ایندکس، مراجعه به جدول را حذف میکند.
تنظیم پارامترهای موتور InnoDB
موتور پیشفرض و توصیهشدهٔ MySQL امروزی InnoDB است. مهمترین پارامتر آن innodb_buffer_pool_size است که تعیین میکند چه مقدار داده و ایندکس در رم کش شود. این تنها پارامتری است که بیشترین تأثیر را بر عملکرد دارد.
innodb_buffer_pool_size = 2G
innodb_buffer_pool_instances = 2
innodb_log_file_size = 512M
innodb_flush_log_at_trx_commit = 2
innodb_flush_method = O_DIRECT
innodb_file_per_table = 1
قانون کلی این است که در سروری اختصاصی برای دیتابیس، innodb_buffer_pool_size را روی حدود ۷۰ درصد رم کل تنظیم کنید. اگر کل دیتاست شما در این بافر جا شود، عملاً همهٔ خواندنها از رم انجام میشود و سرعت چشمگیر میشود.
معنای innodb_flush_log_at_trx_commit
این پارامتر تعادل بین ایمنی داده و سرعت را تعیین میکند. مقدار ۱ (پیشفرض) امنترین است و پس از هر تراکنش روی دیسک مینویسد، اما کندتر است. مقدار ۲ داده را به سیستمعامل میسپارد و در صورت خرابی نرمافزار امن است اما در قطع برق ممکن است یک ثانیه داده از دست برود؛ در ازای آن، عملکرد نوشتن بهشدت بهتر میشود. برای سایتهای محتوایی که ازدسترفتن یک ثانیه داده فاجعه نیست، مقدار ۲ انتخاب خوبی است.
بهینهسازی کوئریها و ساختار جداول
حتی با بهترین ایندکسها، یک کوئری بد نوشتهشده میتواند کند باشد. چند اصل کلیدی در نوشتن کوئری بهینه:
- از SELECT * پرهیز کنید: فقط ستونهای موردنیاز را انتخاب کنید تا داده کمتری منتقل و پردازش شود.
- از LIMIT استفاده کنید: وقتی فقط چند ردیف میخواهید، LIMIT بگذارید تا MySQL زودتر متوقف شود.
- از توابع روی ستون ایندکسشده در WHERE بپرهیزید: نوشتن WHERE YEAR(created_at) = 2026 ایندکس را بیاثر میکند؛ بهجای آن از بازهٔ تاریخ استفاده کنید.
- JOIN را روی ستونهای ایندکسشده انجام دهید: JOIN بدون ایندکس روی جداول بزرگ بسیار کند است.
- نوع دادهٔ مناسب انتخاب کنید: استفاده از INT بهجای BIGINT وقتی نیاز نیست، یا VARCHAR با طول مناسب، حجم و سرعت را بهبود میدهد.
برای مثال، بهجای کوئری غیربهینه که ایندکس را نادیده میگیرد، از بازهٔ صریح استفاده کنید:
-- به جای این:
SELECT id FROM logs WHERE DATE(created_at) = '2026-07-16';
-- این را بنویسید:
SELECT id FROM logs WHERE created_at >= '2026-07-16 00:00:00' AND created_at < '2026-07-17 00:00:00';
پارتیشنبندی جداول بزرگ
وقتی یک جدول به دهها میلیون ردیف میرسد، پارتیشنبندی میتواند کمک کند. با تقسیم جدول بر اساس بازهٔ تاریخ یا محدودهٔ شناسه، MySQL فقط پارتیشن مرتبط را میخواند. این تکنیک برای جداول لاگ و تاریخچه بسیار مؤثر است، اما برای جداول کوچک ارزش پیچیدگی اضافه را ندارد.
مدیریت اتصالات و بافرهای حافظه
علاوه بر Buffer Pool، چند پارامتر دیگر نیز بر عملکرد MySQL تأثیر مستقیم دارند. پارامتر max_connections تعیین میکند چند اتصال همزمان مجاز است. تنظیم آن روی عدد بسیار بالا خطرناک است، چون هر اتصال حافظه مصرف میکند و میتواند رم سرور را تمام کند. فرمول تخمینی حافظهٔ مصرفی هر اتصال شامل بافرهای متعددی مثل sort_buffer_size و join_buffer_size است:
max_connections = 200
sort_buffer_size = 2M
join_buffer_size = 2M
tmp_table_size = 64M
max_heap_table_size = 64M
table_open_cache = 4000
یک اشتباه رایج، افزایش بیش از حد بافرهای هر اتصال است. این بافرها بهازای هر اتصال تخصیص مییابند، پس مقدار ۱۶ مگابایت برای sort_buffer_size ضربدر ۲۰۰ اتصال یعنی ۳.۲ گیگابایت رم فقط برای مرتبسازی. مقادیر متوسط و منطقی معمولاً بهتر از مقادیر بزرگ عمل میکنند. اگر بسیاری از اتصالات بیکار (sleeping) دارید، پارامتر wait_timeout را کاهش دهید تا اتصالات رهاشده زودتر بسته شوند.
استفاده از جداول موقت و مرتبسازی روی دیسک
وقتی MySQL نتواند یک عملیات مرتبسازی یا گروهبندی را در حافظه انجام دهد، آن را روی دیسک انجام میدهد که بهشدت کند است. با بررسی متغیرهای وضعیت میتوانید بفهمید این اتفاق چقدر رخ میدهد:
SHOW GLOBAL STATUS LIKE 'Created_tmp_disk_tables';
SHOW GLOBAL STATUS LIKE 'Sort_merge_passes';
اگر Created_tmp_disk_tables نسبت به کل جداول موقت نسبت بالایی داشته باشد، افزایش tmp_table_size و max_heap_table_size میتواند کمک کند. اما به یاد داشته باشید که ریشهٔ مشکل اغلب کوئریهای بد طراحیشده است؛ بهینهکردن خود کوئری معمولاً مؤثرتر از افزایش بافر است.
نگهداری منظم و پایش دیتابیس
بهینهسازی MySQL یک کار یکباره نیست؛ با رشد داده، آمار ایندکسها کهنه میشود و جداول دچار پراکندگی (fragmentation) میشوند. اجرای منظم دستورهای نگهداری ضروری است:
ANALYZE TABLE orders;
OPTIMIZE TABLE orders;
دستور ANALYZE آمار توزیع داده را بهروز میکند تا بهینهساز کوئری تصمیم بهتری بگیرد. دستور OPTIMIZE فضای بلااستفاده را بازپس میگیرد و ایندکسها را بازسازی میکند. توجه داشته باشید که پس از حذف یا بهروزرسانی حجم زیادی از ردیفها، جدول دچار پراکندگی میشود و فضای فیزیکی روی دیسک بیش از دادههای واقعی باقی میماند؛ اجرای منظم این دستورها این فضا را آزاد میکند و کارایی خواندن را بهبود میبخشد. همچنین بهروز نگهداشتن آمار جدول تضمین میکند که بهینهساز کوئری همیشه بهترین مسیر اجرا را انتخاب کند و بهاشتباه سراغ اسکن کامل جدول نرود. برای پایش سلامت کلی، ابزار رایگان mysqltuner توصیههای ارزشمندی بر اساس وضعیت واقعی سرور شما ارائه میدهد:
wget https://raw.githubusercontent.com/major/MySQLTuner-perl/master/mysqltuner.pl
perl mysqltuner.pl
| شاخص | وضعیت خوب | نیازمند توجه |
|---|---|---|
| نرخ اصابت Buffer Pool | بالای ۹۵٪ | زیر ۹۰٪ |
| کوئریهای کند | نزدیک صفر | افزایش پیوسته |
| Full Table Scans | حداقل | درصد بالا |
| اتصالات همزمان | زیر ۷۰٪ سقف | نزدیک max_connections |
برای اجرای یک دیتابیس پرسرعت به سروری با رم کافی و دیسک NVMe نیاز دارید تا Buffer Pool بهدرستی کار کند و I/O گلوگاه نشود. میتوانید برای خرید سرور مجازی با منابع اختصاصی و دیسک پرسرعت اقدام کنید و مطالب تکمیلی بیشتری را در مقالات رپیدو سرور دنبال کنید.
کش کوئری و استراتژی خواندن و نوشتن
در معماریهای پرترافیک، حتی یک دیتابیس کاملاً بهینه هم ممکن است زیر بار سنگین خواندن به زانو درآید. در این حالت، افزودن یک لایهٔ کش بیرونی مثل Redis برای ذخیرهٔ نتایج کوئریهای پرتکرار، مؤثرترین راهکار است. بهجای اینکه هر بازدید صفحه یک کوئری سنگین اجرا کند، نتیجه یک بار محاسبه و در حافظه ذخیره میشود.
راهکار دیگر برای مقیاسپذیری، تفکیک خواندن از نوشتن (read/write splitting) با استفاده از رونوشتها (replicas) است. در این معماری، یک سرور اصلی (master) همهٔ عملیات نوشتن را انجام میدهد و چند رونوشت فقطخواندنی، بار کوئریهای SELECT را بین خود تقسیم میکنند. این الگو برای سایتهایی که نسبت خواندن به نوشتن بالایی دارند، مثل سایتهای خبری و فروشگاهی، بسیار مؤثر است و امکان رشد افقی را فراهم میکند.
انتخاب موتور ذخیرهسازی درست
گرچه InnoDB برای اکثر کاربردها بهترین انتخاب است، دانستن تفاوت آن با موتورهای دیگر مفید است. InnoDB از تراکنش، قفل در سطح ردیف و کلید خارجی پشتیبانی میکند که آن را برای دادههای حساس و عملیات همزمان ایدهآل میسازد. قفل در سطح ردیف یعنی چند کاربر میتوانند همزمان ردیفهای مختلف یک جدول را تغییر دهند، در حالی که موتور قدیمی MyISAM کل جدول را قفل میکرد و در ترافیک نوشتن بالا گلوگاه جدی ایجاد میکرد. به همین دلیل مهاجرت جداول قدیمی MyISAM به InnoDB اغلب یکی از سادهترین راههای بهبود عملکرد در دیتابیسهای میراثی است. علاوه بر این، InnoDB در برابر خرابی ناگهانی سرور مقاومتر است و با مکانیزم بازیابی خودکار پس از قطع برق، احتمال از دست رفتن یا خرابشدن داده را به حداقل میرساند؛ ویژگی حیاتیای که MyISAM فاقد آن است و میتواند در یک قطعی ناگهانی به فساد کل جدول منجر شود.
در جمعبندی، بهینهسازی MySQL ترکیبی از هنر و علم است: با اندازهگیری دقیق شروع کنید، روی ایندکسگذاری هوشمند و تنظیم Buffer Pool تمرکز کنید، کوئریها را تمیز بنویسید و نگهداری را جدی بگیرید. با این رویکرد میتوانید عملکرد دیتابیس را چند برابر بهبود دهید بدون آنکه لزوماً هزینهٔ سختافزار بیشتری بپردازید.
سوالات متداول
مهمترین پارامتری که باید در MySQL تنظیم کنم کدام است؟
بدون تردید innodb_buffer_pool_size. این پارامتر تعیین میکند چه مقدار داده و ایندکس در رم کش شود. تنظیم آن روی حدود ۷۰ درصد رم در یک سرور اختصاصی دیتابیس، معمولاً بیشترین بهبود عملکرد را با کمترین تلاش ایجاد میکند.
چطور بفهمم کدام کوئریها کند هستند؟
لاگ کوئریهای کند (slow query log) را با long_query_time مناسب فعال کنید و پس از مدتی با mysqldumpslow پرتکرارترین کوئریهای کند را استخراج کنید. سپس با دستور EXPLAIN بفهمید چرا کند هستند و کدام ایندکس کم است.
آیا ایندکس بیشتر همیشه بهتر است؟
خیر. هر ایندکس فضای دیسک مصرف میکند و عملیات نوشتن (INSERT/UPDATE/DELETE) را کند میکند، چون باید بهروز شود. فقط روی ستونهایی که واقعاً در کوئریها استفاده میشوند و کاردینالیتی بالایی دارند ایندکس بگذارید و ایندکسهای بلااستفاده را حذف کنید.
هر چند وقت یکبار باید OPTIMIZE TABLE اجرا کنم؟
برای جداولی که نوشتن و حذف زیاد دارند، اجرای ماهانه یا پس از حذف حجم بزرگی از داده منطقی است. توجه کنید OPTIMIZE میتواند جدول را موقتاً قفل کند، پس آن را در ساعات کمترافیک اجرا کنید و از پشتیبان مطمئن شوید.



