دسته بندی:

دلایل start نشدن سرویس Mysql و راهنمای عیب‌یابی گام‌به‌گام

سایت بالا نمی‌آید و به‌جای صفحه، پیام Error establishing a database connection یا خطای ۵۰۰ برمی‌گردد. اولین حدس معمولاً درست است: سرویس دیتابیس بالا نیست. مهم‌ترین نکته اما همین ابتدای کار است — تا وقتی نمی‌دانید چرا سرویس بالا نمی‌آید، هیچ فایلی را پاک نکنید. در بسیاری از مواردی که به از دست رفتن داده ختم می‌شود، عامل اصلی خرابی اولیه نیست؛ دستورات عجولانه‌ای است که مدیر سرور برای «درست کردن» اجرا می‌کند.

ترتیب زیر را از بالا به پایین جلو بروید. هر گام، اطلاعاتی به شما می‌دهد که برای تصمیم‌گیری در گام مخرب‌تر بعدی لازم است.

گام صفر: نام واقعی سرویس را پیدا کنید

راهنماهای قدیمی همه‌جا service mysqld status می‌نویسند. روی سرورهای امروزی این دستور اغلب پاسخ درستی نمی‌دهد، به دو دلیل:

  • دستور service و مسیر /etc/init.d/ منسوخ شده‌اند. روی توزیع‌های امروزی (CentOS 7 به بعد، AlmaLinux، Rocky Linux، Debian 8 به بعد، Ubuntu 16.04 به بعد) مدیریت سرویس با systemctl انجام می‌شود. service فقط یک پوسته‌ی سازگاری است: اگر یونیت systemd وجود داشته باشد درخواست را به systemctl می‌سپارد، و اگر سرویس هنوز اسکریپت قدیمی /etc/init.d/ داشته باشد همان را صدا می‌زند. چون از قبل نمی‌دانید کدام حالت پیش می‌آید، مستقیم سراغ systemctl بروید تا خروجی و کد خروجی یکنواخت باشد.
  • نام سرویس لزوماً mysqld نیست. روی خانواده‌ی RHEL (CentOS، AlmaLinux، Rocky) و سرورهای cPanel معمولاً MariaDB نصب است و نام سرویس mariadb است. روی Debian و Ubuntu بسته به اینکه MariaDB نصب باشد یا MySQL، نام سرویس mariadb یا mysql است. نام mysqld معمولاً وقتی وجود دارد که MySQL رسمی اوراکل را از مخزن خودش نصب کرده باشید.

پس اول ببینید روی این سرور دقیقاً چه سرویسی وجود دارد:

systemctl list-unit-files | grep -Ei 'mysql|mariadb'

در بقیه‌ی مقاله هرجا mariadb نوشته شده، نام واقعی سرویس سرور خودتان را جایگزین کنید.

گام ۱: وضعیت سرویس را بخوانید

systemctl status mariadb --no-pager -l -n 50

این معادل امروزیِ همان service mysqld status است، با این تفاوت که چند خط آخر لاگ سرویس را هم نشان می‌دهد. -n 50 را به این خاطر اضافه می‌کنیم که systemctl status به‌صورت پیش‌فرض فقط ۱۰ خط آخر را می‌آورد و پیام خطای اصلی معمولاً بالاتر از آن است.

برای بالا آوردن سرویس، هر دو دستور زیر کار می‌کنند؛ restart روی سرویسی که خاموش است دقیقاً مثل start عمل می‌کند. ترجیح با start است، چون اگر سرویس اتفاقاً در حال بالا آمدن یا در میانه‌ی crash recovery باشد، restart همان پروسه را قطع می‌کند:

systemctl start mariadb
# و اگر سرویس بالاست و می‌خواهید تنظیمات جدید اعمال شود:
systemctl restart mariadb

اگر باز هم بالا نیامد، سراغ لاگ کامل systemd بروید:

journalctl -xu mariadb -n 100 --no-pager

(به این نکته دقت کنید که سوئیچ -e در journalctl برای پرش به انتهای خروجی در صفحه‌بند است و کنار --no-pager عملاً بی‌اثر می‌شود؛ به‌جایش از -n استفاده کنید.)

در بیشتر موارد، دلیل واقعی همین‌جا با یک جمله‌ی انگلیسی نوشته شده است. بقیه‌ی این مقاله ترجمه‌ی همان جمله‌ها به کار عملی است.

گام ۲: ارور لاگ درست را پیدا کنید

مسیری که در متن‌های قدیمی تکرار می‌شود اغلب اشتباه تایپ شده است؛ مسیر درست /var/lib/mysql/ است نه /valr/lib/mysql/، و نام فایل هم معمولاً به شکل hostname.err است نه servername.log. بسته به توزیع و بسته‌ای که نصب کرده‌اید، ارور لاگ یکی از این‌هاست:

  • /var/lib/mysql/<hostname>.err — پیش‌فرض رایج MariaDB روی خانواده‌ی RHEL و سرورهای cPanel
  • /var/log/mysqld.log — پیش‌فرض MySQL اوراکل روی CentOS/AlmaLinux/Rocky
  • /var/log/mysql/error.log — پیش‌فرض Debian و Ubuntu
  • /var/log/mariadb/mariadb.log — در بعضی بسته‌های MariaDB

به‌جای حدس زدن، از خود کانفیگ بپرسید:

grep -RhiE '^[[:space:]]*(log[-_]error|datadir|socket|tmpdir)' 
  /etc/my.cnf /etc/my.cnf.d/ /etc/mysql/ 2>/dev/null

و اگر هیچ‌کدام نبود، تازه‌ترین فایل با پسوند err را در دایرکتوری داده پیدا کنید:

ls -lt /var/lib/mysql/*.err
tail -n 100 /var/lib/mysql/*.err

همیشه انتهای فایل را بخوانید، نه ابتدای آن. آخرین بلوکِ مربوط به تلاش ناموفق برای استارت، همان چیزی است که به آن نیاز دارید.

گام ۳: خطای کانفیگ در my.cnf

شایع‌ترین علت، یک خط اشتباه در فایل کانفیگ است — معمولاً بعد از اینکه کسی برای «بهینه‌سازی» مقداری را دست‌کاری کرده. نشانه‌اش در لاگ صریح است:

[ERROR] unknown variable 'query_cache_size=64M'
[ERROR] Aborting

در MySQL 8 تعدادی از متغیرهای قدیمی حذف شده‌اند (از جمله query_cache_size و query_cache_type) و innodb_additional_mem_pool_size در MySQL 5.7 حذف شده است؛ یعنی کانفیگی که سال‌ها روی سرور قدیمی کار می‌کرده، بعد از ارتقا مانع بالا آمدن سرویس می‌شود. (توجه کنید که MariaDB هنوز کوئری‌کش را دارد؛ این حذف مربوط به MySQL است.)

قبل از هر دستوری، اول بدانید فایل کانفیگ سرور شما کجاست. روی خانواده‌ی RHEL و cPanel مسیر /etc/my.cnf است. روی Debian و Ubuntu فایل /etc/mysql/my.cnf است و معمولاً چیزی جز چند خط !includedir ندارد؛ تنظیمات واقعی مثل datadir و socket در فایل‌های داخل /etc/mysql/mysql.conf.d/ یا /etc/mysql/mariadb.conf.d/ هستند. روی RHEL هم کانفیگ بین /etc/my.cnf و فایل‌های /etc/my.cnf.d/ تقسیم شده است. پس ممکن است خط مقصر اصلاً در فایل اصلی نباشد — به همان خروجی grep گام ۲ و به مسیری که خود لاگ اسم برده تکیه کنید.

در متن قدیمی این مقاله توصیه شده بود «تمام محتوای فایل را حذف کنید یا نامش را عوض کنید». محتوای فایل را خالی نکنید. کار درست این است که اول از فایل نسخه‌ی پشتیبان بگیرید و بعد فقط همان خطی را که لاگ اسمش را برده کامنت کنید (مسیر را با مسیر واقعی سرور خودتان جایگزین کنید):

cp -a /etc/my.cnf /etc/my.cnf.bak-$(date +%F)
# سپس خط مقصر را با # کامنت کنید و دوباره استارت بزنید

اگر ناچار شدید کل فایل را کنار بگذارید، تغییر نام (نه حذف) بزنید تا امکان برگرداندن بماند. هشدار: اگر همین فایل مسیر datadir یا socket غیرپیش‌فرض را تعریف کرده باشد، کنار گذاشتنش باعث می‌شود سرویس با مسیر پیش‌فرض بالا بیاید و انگار دیتابیس‌هایتان ناپدید شده‌اند — پس اول مقدار datadir را یادداشت کنید:

mv /etc/my.cnf /etc/my.cnf.bak
systemctl start mariadb

روی MySQL نسخه‌ی ۸٫۰٫۱۶ و بالاتر می‌توانید کانفیگ را بدون استارت واقعی اعتبارسنجی کنید:

mysqld --validate-config

روی MariaDB این سوئیچ وجود ندارد؛ معادل عملی‌اش این است که سرور را در حالت راهنما اجرا کنید تا فایل‌های کانفیگ را بخواند و روی متغیر ناشناخته خطا بدهد:

mysqld --help --verbose > /dev/null

گام ۴: پر شدن فضای دیسک یا اینود

دیتابیس بدون فضای خالی استارت نمی‌شود. نکته‌ی مهم این است که فقط df -h کافی نیست: پارتیشن ممکن است فضای خالی داشته باشد ولی اینودهایش تمام شده باشد و نتیجه‌ی هر دو حالت یکسان است.

df -h /var/lib/mysql
df -ih /var/lib/mysql

اگر پارتیشن جدایی برای /var ندارید، همان df -h و df -ih بدون آرگومان را ببینید. در لاگ، نشانه‌ی این حالت پیام‌هایی مثل No space left on device یا Error writing file است.

برای پیدا کردن مقصر، معمولاً یکی از این‌هاست: بایگانی بک‌آپ‌های قدیمی، لاگ‌های آپاچی، یا باینری‌لاگ‌های خود MySQL در /var/lib/mysql/. اگر باینری‌لاگ‌ها فضا را پر کرده‌اند، آن‌ها را با rm پاک نکنید — بعد از بالا آمدن سرویس با دستور PURGE BINARY LOGS پاکشان کنید، و اگر رپلیکیشن دارید اول مطمئن شوید سرور ثانویه آن لاگ‌ها را خوانده است.

گام ۵: مالکیت و دسترسی /tmp و دایرکتوری داده

MySQL برای جدول‌های موقت به /tmp نیاز دارد. اگر مالکیت یا پرمیژن آن به‌هم ریخته باشد، سرویس با خطای دسترسی می‌ایستد. حالت درست این است:

chown root:root /tmp
chmod 1777 /tmp

رقم اول یعنی sticky bit؛ بدون آن هر کاربری می‌تواند فایل بقیه را در /tmp پاک کند. اگر روی سرور cPanel هستید، /tmp ممکن است یک فایل‌سیستم جدا روی /usr/tmpDSK باشد که مستقل از دیسک اصلی پر می‌شود؛ آن را هم با df -h /tmp چک کنید.

مورد شایع دیگر، به‌هم ریختن مالکیت دایرکتوری داده است — معمولاً بعد از برگرداندن بک‌آپ یا کپی فایل‌ها با کاربر root. قبل از اجرای دستور زیر، مسیر واقعی datadir را از کانفیگ تأیید کنید (اگر مسیر را اشتباه بدهید، مالکیت یک شاخه‌ی نامربوط را خراب می‌کنید):

chown -R mysql:mysql /var/lib/mysql

اگر SELinux فعال است (getenforce جواب Enforcing بدهد) و فایل‌ها را از جای دیگری کپی کرده‌اید، برچسب امنیتی هم باید ترمیم شود:

restorecon -Rv /var/lib/mysql

گام ۶: پروسه‌ی سرگردان، سوکت و فایل PID جامانده

گاهی سرویس بالا نمی‌آید چون از دید سیستم هنوز پایین نیامده است. اول ببینید پروسه‌ای در حال اجراست یا نه:

ps aux | grep [m]ysqld

(نگارش درست ps aux است؛ ps -aux در متن قدیمی نگارش مبهمی است و در برخی نسخه‌ها هشدار می‌دهد یا معنای دیگری می‌گیرد. براکت در [m]ysqld باعث می‌شود خودِ دستور grep در نتیجه نیاید.)

قبل از اینکه هر پروسه‌ای را بکشید، این را بخوانید: kill -9 — که در متن قدیمی این مقاله به‌عنوان راه‌حل معمول پیشنهاد شده بود — روی یک دیتابیس خطرناک است. سیگنال ۹ به سرویس فرصت بستن فایل‌ها را نمی‌دهد و نتیجه‌اش یک استارت بعدیِ همراه با crash recovery و در بدترین حالت خرابی داده است. مخصوصاً اگر در لاگ می‌بینید که InnoDB مشغول Starting crash recovery است، آن را نکشید؛ روی دیتابیس‌های بزرگ این مرحله ممکن است ده‌ها دقیقه طول بکشد و درصد پیشرفتش را در همان لاگ می‌نویسد. صبر کردن اینجا همیشه ارزان‌تر از بازیابی است.

پس اگر پروسه‌ای هست، اول خاتمه‌ی تمیز بدهید. خود systemctl stop منتظر می‌ماند تا سرویس واقعاً بسته شود:

systemctl stop mariadb

بعد دوباره وضعیت را ببینید. اگر هنوز پروسه‌ای مانده بود، یک SIGTERM دستی بفرستید و چند ثانیه صبر کنید. سوئیچ -x باعث می‌شود فقط پروسه‌ای با نام دقیق mysqld هدف بگیرد و پروسه‌هایی مثل mysqld_safe یا mysqld_exporter ناخواسته کشته نشوند:

pkill -x mysqld
sleep 10
ps aux | grep [m]ysqld

و فقط اگر بعد از چند دقیقه هنوز پروسه زنده بود و از لاگ مطمئن شدید که در حال crash recovery نیست، سراغ روش قهری بروید:

killall -9 mysqld

بعد از اطمینان از نبودن پروسه، فایل PID و سوکت جامانده را می‌توانید حذف کنید. این دو فایل داده نیستند و با استارت بعدی دوباره ساخته می‌شوند. اول ببینید کجا هستند:

ls -l /var/lib/mysql/*.pid /var/lib/mysql/mysql.sock
ls -l /run/mysqld/

روی RHEL و مشتقاتش سوکت معمولاً /var/lib/mysql/mysql.sock و روی Debian/Ubuntu معمولاً /run/mysqld/mysqld.sock است. اگر دایرکتوری /run/mysqld اصلاً وجود ندارد، بسازیدش و مالکیتش را درست کنید:

mkdir -p /run/mysqld
chown mysql:mysql /run/mysqld

توجه: /run یک فایل‌سیستم موقت در حافظه است و با هر ری‌استارت خالی می‌شود. این کار فقط برای همین یک بار جواب می‌دهد؛ راه‌حل دائمی، فایل مربوط به این سرویس در /usr/lib/tmpfiles.d/ است که بسته‌ی رسمی آن را نصب می‌کند. اگر مجبور شدید این پوشه را دستی بسازید، یعنی نصب بسته ناقص است و بهتر است بسته را دوباره نصب کنید.

گام ۷: کمبود حافظه و پورت اشغال‌شده

اگر لاگ حرفی از کانفیگ و دیسک نمی‌زند اما سرویس بی‌صدا می‌میرد، دو گزینه‌ی بعدی را ببینید. اول اینکه کرنل به‌خاطر کمبود RAM پروسه را کشته باشد:

free -m
journalctl -k -b | grep -i -E 'oom|killed process'

روی سرورهای کم‌رم، مقدار بزرگ innodb_buffer_pool_size رایج‌ترین علت است؛ کمش کنید و دوباره استارت بزنید.

دوم اینکه پورت دیتابیس را سرویس دیگری (یا نمونه‌ی دومی از خود MySQL) گرفته باشد. خطای متناظرش در لاگ Address already in use است:

ss -lntp | grep -w 3306

گام ۸: خرابی InnoDB

اگر لاگ پر است از InnoDB: Database page corruption یا سرویس هنگام recovery می‌افتد، وارد جدی‌ترین بخش شده‌اید. قبل از هر کاری، وقتی سرویس خاموش است یک کپی سرد از کل دایرکتوری داده بگیرید. اول اندازه‌ی دایرکتوری و فضای مقصد را بسنجید — اگر مشکل اصلی پر بودن دیسک بوده، گرفتن بک‌آپ روی همان دیسک هم شکست می‌خورد و هم اوضاع را بدتر می‌کند. مقصد را روی یک فایل‌سیستم دیگر بگذارید:

systemctl stop mariadb
du -sh /var/lib/mysql
df -h /backup
tar czf /backup/mysql-datadir-$(date +%F).tar.gz -C /var/lib mysql

سپس می‌توانید موقتاً حالت بازیابی اجباری را فعال کنید. بلوک زیر را در فایل کانفیگ اضافه کنید:

[mysqld]
innodb_force_recovery = 1

از مقدار ۱ شروع کنید و فقط در صورت نیاز یکی‌یکی بالا ببرید. هدف این نیست که سرور با این تنظیم کار کند؛ هدف فقط این است که به‌اندازه‌ی گرفتن خروجی بالا بیاید. چون در این حالت نوشتن مسدود است، --skip-lock-tables را اضافه کنید تا mysqldump سر قفل‌گذاری روی جدول‌ها شکست نخورد:

mysqldump -u root -p --all-databases --skip-lock-tables > /root/all-databases.sql

بعد از گرفتن خروجی، خط innodb_force_recovery را حذف کنید. نکات ایمنی این مرحله:

  • با هر مقدار بزرگ‌تر از صفر، عملیات نوشتن (INSERT، UPDATE، DELETE) مسدود می‌شود؛ این حالت برای سرویس‌دهی عادی نیست.
  • مقادیر ۴ به بالا می‌توانند به داده آسیب دائمی بزنند و فقط برای استخراج نهایی داده به کار می‌روند. تا وقتی بک‌آپ سرد نگرفته‌اید سراغشان نروید.
  • هرگز ibdata1 را پاک نکنید. این فایل دیکشنری داده‌ی InnoDB را نگه می‌دارد؛ بدون آن، فایل‌های جدول (table_name.ibd) عملاً غیرقابل استفاده می‌شوند. حذف فایل‌های redo log هم ترفندی مربوط به نسخه‌های خیلی قدیمی است که امروز لازم نیست و بعد از کرش خطرناک است.

چطور مطمئن شویم واقعاً درست شده است

بالا آمدن سرویس با «بدون خطا برگشتن دستور» یکی نیست. این موارد را چک کنید:

systemctl status mariadb --no-pager
mysql -u root -p -e "SELECT VERSION(); SHOW DATABASES;"

و در ارور لاگ دنبال خط ready for connections با تاریخ همین چند دقیقه‌ی پیش بگردید. در آخر مطمئن شوید سرویس بعد از ری‌استارت سرور هم خودکار بالا می‌آید:

systemctl is-enabled mariadb
systemctl enable mariadb

اگر سرویس بعد از یک کرش بالا آمده، سلامت جدول‌ها را هم بررسی کنید. این دستور فقط می‌خواند و چیزی را تغییر نمی‌دهد:

mysqlcheck -u root -p --all-databases --check

کارهایی که در این وضعیت نباید انجام دهید

  • پاک کردن فایل‌های داخل /var/lib/mysql/ به امید «تمیز شدن». دیتابیس‌های شما همان‌جا هستند.
  • kill -9 زدن به‌عنوان اولین قدم، مخصوصاً وسط crash recovery.
  • خالی کردن محتوای my.cnf به‌جای کامنت کردن خط مشکل‌دار.
  • اجرای دستوری که معنایش را نمی‌دانید، فقط چون در نتیجه‌ی جستجو دیده‌اید. لاگ را بخوانید؛ همیشه ارزان‌تر از بازیابی بک‌آپ است.
  • باقی گذاشتن innodb_force_recovery در کانفیگ بعد از حل مشکل.

جمع‌بندی

ترتیب امن عیب‌یابی همیشه یکی است: نام سرویس را پیدا کنید، systemctl status و journalctl را بخوانید، ارور لاگ درست را پیدا کنید، بعد سراغ کانفیگ، فضای دیسک و اینود، دسترسی /tmp و دایرکتوری داده، پروسه و سوکت جامانده، حافظه و پورت، و در نهایت InnoDB بروید. هر گام مخرب باید بعد از یک بک‌آپ انجام شود، نه قبل از آن. اگر بعد از این مراحل هم سرویس بالا نیامد، پیام دقیق ارور لاگ را برای پشتیبانی بفرستید؛ همان چند خط، تفاوت بین یک تیکت ده‌دقیقه‌ای و یک شب بی‌خوابی است.