یک شب جمعه، سرور پروژه‌ای که هفته‌ها بی‌دردسر کار کرده بود، شروع کرد به برگرداندن خطا در همه‌ی صفحات. در لاگ، پیام آشنا دیده می‌شد: SQLSTATE[HY000] [2002] Can't connect to MySQL server on '127.0.0.1' (111). تا آن روز، این خطا را به‌عنوان یک مشکل رمز یا کاربر می‌شناختم که با یک بار بررسی حل می‌شود. آن شب فهمیدم که خطای Can't connect to MySQL server در MySQL، در باطن، پنجره‌ای است به سمت معماری شبکه، پیکربندی سرور، و لایه‌های ارتباطی که بین اپلیکیشن و دیتابیس قرار دارند.

خطای Can't connect to MySQL server دقیقاً چیست؟

MySQL به‌عنوان یکی از محبوب‌ترین سیستم‌های مدیریت دیتابیس رابطه‌ای، در لایه‌ای مستقل از اپلیکیشن اجرا می‌شود. وقتی اپلیکیشن شما (PHP، Python، Java، یا هر زبان دیگر) تلاش می‌کند به MySQL متصل شود، مرورگر فرآیند اتصال از چند مرحله‌ی مشخص عبور می‌کند: حل نام هاست، برقراری اتصال TCP (یا socket)، تبادل پیام‌های احراز هویت، و در نهایت فعال‌سازی جلسه. اگر هر یک از این مراحل با شکست مواجه شود، خطای زیر مطرح می‌شود:

SQLSTATE[HY000] [2002] Can't connect to MySQL server on '127.0.0.1' (111)

# یا

ERROR 2003 (HY000): Can't connect to MySQL server on 'localhost' (111)

# یا

mysqli_connect(): (HY000/2002): Connection refused

پیام خطا چند بخش دارد. بخش اول، کد SQLSTATE و شماره خطا است. بخش دوم، هاست و پورتی که MySQL در آن جستجو شده. بخش سوم، در پرانتز، شماره خطای سیستمی (errno) است که منبع دقیق شکست را نشان می‌دهد. مثلاً (111) در Linux به‌معنای Connection refused است، یعنی سرور MySQL روی آن پورت گوش نمی‌دهد یا اتصال را رد می‌کند.

نکته‌ی مهم این است که این خطا در لایه‌ی شبکه رخ می‌دهد، نه در لایه‌ی احراز هویت. یعنی حتی اگر رمز و نام کاربری درست باشند، این خطا می‌تواند رخ دهد. این تفاوت بنیادین با خطای Access denied for user در MySQL است که در لایه‌ی احراز هویت رخ می‌دهد.

اگر با مبانی MySQL آشنایی ندارید، ابتدا یادگیری MySQL از صفر را بخوانید تا مدل ذهنی درستی از معماری دیتابیس شکل بگیرد. درک این خطا بدون فهم لایه‌های اتصال ممکن نیست.

خطای Can't connect یک شکایت از مسیر است، نه از هویت. MySQL می‌گوید تو خودت را درست معرفی کردی، ولی صدای تو به من نرسید. راه‌حل، بررسی مسیر شبکه است نه سرزنش رمز.

تفاوت این خطا با Access denied

یکی از پرتکرارترین سؤالات، تفاوت Can't connect to MySQL server و Access denied for user است. درک این تفاوت، اولین گام در تشخیص سریع است:

Can't connect to MySQL server: خطای لایه‌ی شبکه. یعنی MySQL در آن آدرس و پورت در دسترس نیست. دلایل: سرویس خاموش، پورت اشتباه، فایروال، bind-address، یا مشکل شبکه.

Access denied for user: خطای لایه‌ی احراز هویت. یعنی MySQL در دسترس است ولی اعتبار کاربر رد شده. دلایل: رمز اشتباه، هاست اشتباه، یا کاربر وجود ندارد.

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

کدهای خطا و پیام‌های مختلف

خطای Can't connect to MySQL server در نسخه‌های مختلف MySQL، پیام‌ها و کدهای متفاوتی دارد. شناخت این تفاوت‌ها، در تشخیص سریع کمک‌کننده است:

کد خطا پیام معنی
2002 Can't connect to MySQL server on 'host' (errno) شکست در برقراری اتصال TCP یا socket
2003 Can't connect to MySQL server on 'host' (111) Connection refused - سرور گوش نمی‌دهد
2005 Unknown MySQL server host 'host' نام هاست قابل حل نیست (DNS)
2006 MySQL server has gone away ارتباط قطع شد وسط کار
2013 Lost connection to MySQL server during query ارتباط در حین اجرای کوئری قطع شد
1040 Too many connections ظرفیت اتصال‌ها پر شده
2002/110 Connection timed out پاسخی در زمان مشخص دریافت نشد

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

چرا MySQL این خطا را مطرح می‌کند؟

MySQL به‌طور طراحی‌شده، خطای Can't connect را در شرایطی مطرح می‌کند که اتصال از دید شبکه امکان‌پذیر نیست. این تصمیم، از چند اصل بنیادین می‌آید:

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

دو: بازخورد سریع. اگر اپلیکیشن سعی کند به MySQL متصل شود و نتواند، بهتر است سریع خطا بدهد تا کاربر مدت طولانی منتظر نماند. MySQL با خطای واضح Can't connect این بازخورد سریع را فراهم می‌کند.

سه: امنیت شبکه. MySQL به‌طور پیش‌فرض فقط از localhost گوش می‌دهد. این تصمیم امنیتی، از دسترسی غیرمجاز جلوگیری می‌کند ولی در عین حال، در پروژه‌های چند-سروری نیاز به پیکربندی صریح دارد.

در چارچوب کلی MySQL، خطای Can't connect بخشی از ساختار دفاعی این دیتابیس است. این خطا، شما را وادار می‌کند درباره‌ی معماری شبکه، پیکربندی سرور، و لایه‌های اتصال صریح باشید. همین فلسفه در سایر خطاهای MySQL مثل خطای Unknown database در MySQL هم دیده می‌شود.

نُه علت رایج این خطا

در تجربه‌ی من روی صدها پروژه‌ی MySQL، خطای Can't connect to MySQL server از نُه علت مشخص می‌آید. شناخت این علت‌ها، تشخیص را در چند ثانیه ممکن می‌کند.

  1. سرویس MySQL خاموش است: شایع‌ترین علت.
  2. هاست یا پورت اشتباه: پیکربندی نادرست در فایل اتصال.
  3. bind-address محدودکننده: MySQL فقط از localhost گوش می‌دهد.
  4. فایروال جلوی پورت 3306: اتصال از راه دور مسدود است.
  5. سوکت Unix پیدا نمی‌شود: مسیر سوکت تغییر کرده.
  6. حداکثر اتصال‌ها پر شده: max_connections به سقف رسیده.
  7. مشکل DNS: نام هاست قابل حل نیست.
  8. دیسک پر شده: MySQL به دلیل نبود فضا متوقف شده.
  9. پیکربندی اشتباه در Docker یا Kubernetes: نام سرویس اشتباه.

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

سرویس MySQL خاموش است

شایع‌ترین منبع این خطا، خاموش بودن سرویس MySQL است. این حالت در شرایط زیر رخ می‌دهد:

  • سرور restart شده ولی MySQL خودکار شروع نشده.
  • MySQL به دلیل خطای داخلی crash کرده.
  • سرویس به‌طور دستی متوقف شده.
  • در Docker، container دیتابیس متوقف شده.

نشانه‌ها:

# در Linux با systemd
sudo systemctl status mysql
# یا
sudo systemctl status mariadb

# خروجی:
# ● mysql.service - MySQL Community Server
#    Active: inactive (dead)

راه‌حل: شروع سرویس MySQL:

sudo systemctl start mysql
sudo systemctl enable mysql  # شروع خودکار در بوت

در macOS:

brew services start mysql

در Docker:

docker start mysql_container
docker ps  # بررسی وضعیت

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

sudo tail -100 /var/log/mysql/error.log

مبانی مدیریت سرویس در مدیریت کاربران MySQL آمده است. برای درک بهتر سرور، سرور چیست و چگونه کار می‌کند نقطه‌ی شروع خوبی است.

هاست یا پورت اشتباه

دومین منبع شایع، هاست یا پورت اشتباه در فایل پیکربندی اپلیکیشن است. الگوی کلاسیک:

# در wp-config.php
define("DB_HOST", "localhost:3307");  // پورت اشتباه

# یا در Django settings.py
DATABASES = {
    "default": {
        "HOST": "db.example.com",  // هاست اشتباه
        "PORT": "3306",
    }
}

نکته‌ی مهم: پورت پیش‌فرض MySQL 3306 است. اگر سرور شما MySQL را روی پورت دیگری اجرا می‌کند (مثلاً برای جلوگیری از حملات)، باید پورت را در فایل پیکربندی به‌درستی وارد کنید.

راه‌حل: بررسی پورت واقعی MySQL:

sudo netstat -tlnp | grep mysql
# یا
sudo ss -tlnp | grep mysql

خروجی:

tcp        0      0 127.0.0.1:3306          0.0.0.0:*               LISTEN      1234/mysqld

در این مثال، MySQL روی پورت 3306 گوش می‌دهد. اگر پورت دیگری بود (مثل 3307)، باید در فایل پیکربندی اپلیکیشن همان پورت وارد شود.

نکته‌ی ظریف: در بعضی هاست‌های اشتراکی، پورت MySQL غیر از 3306 است. قبل از هر اتصال، پورت صحیح را از پنل هاست (cPanel یا DirectAdmin) دریافت کنید. مبانی هاست در هاست چیست و چگونه انتخاب کنیم آمده است.

bind-address و عدم دسترسی از راه دور

سومین منبع، پیکربندی bind-address است. این تنظیم تعیین می‌کند که MySQL روی کدام رابط‌های شبکه گوش دهد:

# در /etc/mysql/my.cnf یا /etc/my.cnf
[mysqld]
bind-address = 127.0.0.1

اگر bind-address روی 127.0.0.1 باشد، MySQL فقط از همان سرور متصل می‌شود. اتصال از سرور دیگر، خطای Can't connect می‌دهد:

ERROR 2003 (HY000): Can't connect to MySQL server on '10.0.0.5' (111)

راه‌حل: تغییر bind-address به 0.0.0.0 یا IP مشخص:

bind-address = 0.0.0.0

سپس MySQL را restart کنید:

sudo systemctl restart mysql

نکته‌ی امنیتی: با تنظیم bind-address = 0.0.0.0، MySQL روی همه‌ی رابط‌ها گوش می‌دهد. این رویکرد از نظر امنیتی خطرناک است، چون هر IP می‌تواند به MySQL متصل شود. راه‌حل امن: فایروال را نیز تنظیم کنید تا فقط از IPهای معتبر دسترسی پذیرفته شود. مبانی امنیت در مباحث مربوط به سرور آمده است.

فایروال و مسدود بودن پورت 3306

چهارمین منبع، فایروال است. حتی اگر MySQL روی همه‌ی رابط‌ها گوش دهد، فایروال می‌تواند جلوی اتصال را بگیرد:

# در سرور MySQL
sudo iptables -L -n | grep 3306
# اگر خطایی نبود، پورت مسدود است

در سرورهای ابری (AWS، GCP، Azure)، باید علاوه بر فایروال سرور، Security Group یا Network ACL را هم بررسی کنید. نشانه‌ها:

  • اتصال از localhost کار می‌کند ولی از راه دور نه.
  • اتصال از یک IP خاص کار می‌کند ولی از IP دیگر نه.
  • در لاگ MySQL، تلاش‌های اتصال از راه دور ثبت نمی‌شوند (چون فایروال جلوی رسیدن بسته‌ها را می‌گیرد).

راه‌حل: باز کردن پورت 3306 برای IPهای معتبر:

# ufw (Ubuntu)
sudo ufw allow from 10.0.0.5 to any port 3306

# firewalld (CentOS/RHEL)
sudo firewall-cmd --permanent --add-rich-rule='
    rule family="ipv4"
    source address="10.0.0.5"
    port protocol="tcp" port="3306" accept'
sudo firewall-cmd --reload

نکته‌ی امنیتی: هرگز پورت 3306 را به‌طور کامل باز نکنید (0.0.0.0/0). این رویکرد، باعث می‌شود MySQL در معرض حملات Brute Force قرار گیرد. همیشه از IP مشخص یا شبکه‌ی خصوصی استفاده کنید. مبانی فایروال در مباحث امنیت سرور آمده است.

سوکت Unix پیدا نمی‌شود

پنجمین منبع، مربوط به سوکت Unix است. در MySQL، وقتی از localhost استفاده می‌کنید، اتصال معمولاً از طریق سوکت Unix برقرار می‌شود، نه TCP/IP:

ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock' (2)

مشکل: سوکت در مسیر مشخص وجود ندارد. دلایل:

  • MySQL خاموش است.
  • مسیر سوکت تغییر کرده. در پیکربندی، مسیر متفاوتی تنظیم شده.
  • نسخه‌ی اپلیکیشن، مسیر سوکت را اشتباه جستجو می‌کند. مثلاً PHP در مسیر پیش‌فرض MySQL جستجو می‌کند ولی MySQL سوکت را در مسیر دیگری ساخته.

راه‌حل: بررسی مسیر سوکت واقعی:

# در فایل پیکربندی MySQL
cat /etc/mysql/my.cnf | grep socket
# معمولاً:
# socket = /var/run/mysqld/mysqld.sock

و در پیکربندی اپلیکیشن، همان مسیر را وارد کنید:

# در PHP
$conn = new mysqli("localhost", "dbuser", "password", "mydb", null, "/var/run/mysqld/mysqld.sock");

# در PDO
$pdo = new PDO(
    "mysql:unix_socket=/var/run/mysqld/mysqld.sock;dbname=mydb",
    "dbuser",
    "password"
);

مبانی PDO در آموزش PDO در PHP آمده است.

تفاوت socket و TCP در اتصال

یکی از مفاهیم ظریف MySQL، تفاوت اتصال از طریق Unix socket و TCP است:

Unix socket: یک فایل ویژه در فایل‌سیستم که به‌عنوان نقطه‌ی تبادل داده استفاده می‌شود. اتصال از این طریق، فقط از همان سرور امکان‌پذیر است. مزیت: سریع‌تر از TCP، چون از پشته‌ی شبکه عبور نمی‌کند.

TCP: اتصال از طریق پورت شبکه (معمولاً 3306). اتصال از راه دور هم امکان‌پذیر است. معایب: کمی کندتر از socket.

نکته‌ی مهم: وقتی در پیکربندی اپلیکیشن از localhost استفاده می‌کنید، MySQL معمولاً socket را انتخاب می‌کند. اگر می‌خواهید از TCP استفاده کنید، باید از 127.0.0.1 استفاده کنید:

# socket
DB_HOST=localhost

# TCP
DB_HOST=127.0.0.1

این تفاوت، منبع شایع خطاهای Can't connect و Access denied for user در MySQL است. اگر بعد از تغییر اتصال از localhost به 127.0.0.1، خطا گرفتید، احتمالاً مجوزهای کاربر برای هاست جدید تعریف نشده است.

Max connections و پر شدن ظرفیت

ششمین منبع، پر شدن ظرفیت اتصال‌هاست. MySQL یک متغیر پیکربندی به‌نام max_connections دارد که تعیین می‌کند حداکثر چند اتصال همزمان پذیرفته می‌شود:

-- بررسی مقدار فعلی
SHOW VARIABLES LIKE "max_connections";
-- خروجی: 151

-- بررسی اتصال‌های فعال
SHOW PROCESSLIST;
-- یا
SHOW STATUS LIKE "Threads_connected";

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

ERROR 1040 (HY000): Too many connections

دلایل رایج:

  • نشت اتصال در اپلیکیشن. اتصال‌ها بسته نمی‌شوند و در pool باقی می‌مانند.
  • افزایش ترافیک. تعداد کاربران همزمان بیشتر از ظرفیت.
  • کوئری‌های طولانی. اتصال‌ها به‌دلیل کوئری‌های سنگین معطل می‌مانند.
  • مهاجم یا بات. تلاش برای اتصال‌های مکرر.

راه‌حل:

-- افزایش ظرفیت موقت
SET GLOBAL max_connections = 300;

-- در فایل پیکربندی برای دائمی کردن
[mysqld]
max_connections = 300

نکته‌ی مهم: افزایش max_connections بدون مدیریت مصرف حافظه می‌تواند به crash سرور منجر شود. هر اتصال، حافظه مصرف می‌کند. در سرورهای محدود، این رویکرد خطرناک است. راه‌حل بهتر: مدیریت صحیح connection pool در اپلیکیشن و بستن اتصال‌های بی‌استفاده. مبانی بهینه‌سازی در بهینه‌سازی کوئری‌های MySQL آمده است.

مشکل در حل نام هاست

هفتمین منبع، مشکل در حل نام هاست (DNS) است. اگر هاست در فایل پیکربندی، نام (نه IP) باشد و آن نام قابل حل نباشد:

ERROR 2005 (HY000): Unknown MySQL server host 'db.example.com' (11001)

دلایل:

  • نام هاست اشتباه است.
  • DNS server در دسترس نیست.
  • نام هاست منقضی شده.

راه‌حل: تست DNS از سرور اپلیکیشن:

nslookup db.example.com
# یا
dig db.example.com

# خروجی مطلوب:
# db.example.com.  300  IN  A  10.0.0.5

اگر DNS جواب نداد، از IP مستقیم در پیکربندی استفاده کنید:

DB_HOST=10.0.0.5

نکته‌ی ظریف: در Kubernetes، استفاده از IP می‌تواند مشکل‌ساز باشد چون Podها IP خود را تغییر می‌دهند. راه‌حل: استفاده از نام سرویس Kubernetes. مبانی در مباحث Kubernetes آمده است.

دیسک پر و توقف MySQL

هشتمین منبع، پر شدن دیسک است. اگر دیسک سرور پر شود، MySQL نمی‌تواند بنویسد و ممکن است متوقف شود:

ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock' (2)

نشانه‌ها:

df -h
# Filesystem      Size  Used Avail Use% Mounted on
# /dev/sda1        50G   50G     0 100% /

راه‌حل: آزاد کردن فضا در سرور:

  • حذف فایل‌های لاگ قدیمی.
  • حذف بکاپ‌های قدیمی.
  • پاک‌سازی جدول‌های موقت MySQL.
  • بازبینی جداول InnoDB و ibdata.
# بررسی حجم دیتابیس‌ها
du -sh /var/lib/mysql/*

# بررسی جداول بزرگ
mysql -e "SELECT table_schema, table_name,
    ROUND(((data_length + index_length) / 1024 / 1024), 2) AS size_mb
    FROM information_schema.TABLES
    ORDER BY size_mb DESC LIMIT 20;"

نکته‌ی پیشگیرانه: مانیتورینگ مداوم فضای دیسک، بخشی از نگهداری سرور است. تنظیم آلارم برای دیسک‌های بیش از 80 درصد پر، از بروز این خطا جلوگیری می‌کند. مبانی بهینه‌سازی دیتابیس در بهینه‌سازی جداول MySQL آمده است.

خطای اتصال در وردپرس

در وردپرس، خطای Can't connect با پیام زیر نمایش داده می‌شود:

Error establishing a database connection

سه دلیل اصلی:

  1. اطلاعات دیتابیس در wp-config.php اشتباه است.
  2. سرویس MySQL خاموش است.
  3. هاست یا پورت اشتباه است.

راه‌حل: بررسی و اصلاح اطلاعات اتصال در wp-config.php:

define("DB_NAME", "wordpress_db");
define("DB_USER", "wordpress_dbuser");
define("DB_PASSWORD", "your_password");
define("DB_HOST", "localhost");  // یا 127.0.0.1 یا IP سرور

نکته‌ی مهم: در وردپرس، اگر DB_HOST روی localhost باشد، از socket استفاده می‌کند. اگر روی 127.0.0.1 باشد، از TCP. اگر خطای Can't connect گرفتید، اول از یک اسکریپت PHP ساده برای تست اتصال استفاده کنید:

<?php
$conn = new mysqli("localhost", "wordpress_dbuser", "your_password", "wordpress_db");
if ($conn->connect_error) {
    die("Connection failed: " . $conn->connect_error);
}
echo "Connected successfully";
?>

اگر این اسکریپت خطا داد، مشکل در MySQL یا پیکربندی است. اگر موفق شد، مشکل در wp-config.php است. مبانی وردپرس در وردپرس چیست و چگونه شروع کنیم آمده است.

در PHP، PDO و mysqli

در PHP، خطای Can't connect از دو مسیر می‌آید:

در mysqli

$conn = new mysqli("localhost", "dbuser", "password", "mydb");
if ($conn->connect_error) {
    // Connection refused یا Can't connect to MySQL server
    die("Connection failed: " . $conn->connect_error);
}

در PDO

try {
    $pdo = new PDO(
        "mysql:host=localhost;port=3306;dbname=mydb;charset=utf8mb4",
        "dbuser",
        "password",
        [PDO::ATTR_TIMEOUT => 5]
    );
} catch (PDOException $e) {
    // SQLSTATE[HY000] [2002] Can't connect
    error_log($e->getMessage());
}

نکته‌ی مهم: تنظیم PDO::ATTR_TIMEOUT باعث می‌شود اپلیکیشن مدت طولانی منتظر نماند اگر MySQL در دسترس نباشد. مقدار 5 ثانیه، تعادل خوبی بین انتظار و بازخورد سریع است. مبانی کامل در اتصال PHP به MySQL و آموزش PDO در PHP آمده است.

در Docker و Kubernetes

در محیط‌های container-based، خطای Can't connect از منابع خاص خود می‌آید:

در Docker

# اشتباه: استفاده از localhost
DB_HOST=localhost  # به خود container اشاره می‌کند نه به MySQL

# درست: استفاده از نام سرویس
DB_HOST=db  # نام سرویس در docker-compose.yml

# docker-compose.yml
services:
  db:
    image: mysql:8
    environment:
      MYSQL_ROOT_PASSWORD: root_pass
      MYSQL_DATABASE: mydb
      MYSQL_USER: dbuser
      MYSQL_PASSWORD: user_pass
    ports:
      - "3306:3306"
  app:
    depends_on:
      - db
    environment:
      DB_HOST: db  # نام سرویس، نه localhost
      DB_PORT: 3306

نکته‌ی مهم: در Docker، اپلیکیشن و MySQL در containerهای جداگانه هستند. localhost در اپلیکیشن به خودش اشاره می‌کند، نه به MySQL. راه‌حل: استفاده از نام سرویس.

در Kubernetes

# نام سرویس MySQL
DB_HOST=mysql-service
# یا نام کامل با namespace
DB_HOST=mysql-service.default.svc.cluster.local

نکته: در Kubernetes، اگر MySQL به‌عنوان StatefulSet اجرا می‌شود، نام سرویس معمولاً mysql-0.mysql-headless یا مشابه است. مبانی Kubernetes در مباحث مربوط به orchestration آمده است.

روش تشخیص اصولی در شش گام

در تجربه‌ی من، تشخیص خطای Can't connect در چند دقیقه انجام می‌شود، اگر روش سیستماتیک داشته باشید:

گام اول: خواندن دقیق پیام خطا. پیام MySQL دقیقاً می‌گوید کدام هاست و پورت جستجو شده و چه کد خطای سیستمی داده شده. این داده‌ها، جهت جستجو را تعیین می‌کند.

گام دوم: بررسی وضعیت سرویس MySQL. اگر سرویس خاموش است، خطا از همان‌جاست:

sudo systemctl status mysql
# یا
sudo systemctl status mariadb

گام سوم: بررسی پورت MySQL. اگر سرویس در حال اجراست، پورت را بررسی کنید:

sudo ss -tlnp | grep mysql
sudo netstat -tlnp | grep mysql

گام چهارم: تست اتصال از CLI. از سرور اپلیکیشن، تست اتصال به MySQL:

mysql -u dbuser -p -h host -P port mydb

اگر از CLI اتصال برقرار شد ولی از اپلیکیشن نه، مشکل در پیکربندی اپلیکیشن است.

گام پنجم: بررسی فایروال. اگر اتصال از راه دور است، فایروال را بررسی کنید:

sudo iptables -L -n | grep 3306
sudo ufw status

گام ششم: بررسی لاگ MySQL. در بعضی موارد، لاگ MySQL اطلاعات دقیق‌تری دارد:

sudo tail -100 /var/log/mysql/error.log

ابزارهای تشخیص:

  • systemctl status mysql: بررسی وضعیت سرویس.
  • ss -tlnp: بررسی پورت‌های گوش‌دهنده.
  • mysql -u user -p -h host: تست اتصال از CLI.
  • nslookup و dig: بررسی DNS.
  • telnet host 3306: تست ارتباط با پورت.
  • tcpdump: بررسی ترافیک شبکه.

مبانی عیب‌یابی در مباحث مربوط به سرور و دیتابیس آمده است.

راهبردهای رفع اصولی

بعد از تشخیص، نوبت به رفع می‌رسد. راهبردهای رفع، بر اساس نوع خطا متفاوت است:

راهبرد اول: شروع مجدد سرویس MySQL

sudo systemctl restart mysql
# یا
sudo systemctl restart mariadb

راهبرد دوم: بررسی و اصلاح پیکربندی

در فایل پیکربندی اپلیکیشن، هاست، پورت، نام دیتابیس، و رمز را بررسی کنید:

# در .env
DB_HOST=127.0.0.1
DB_PORT=3306
DB_NAME=mydb
DB_USER=dbuser
DB_PASSWORD=password

راهبرد سوم: بررسی و اصلاح bind-address

# در /etc/mysql/my.cnf
[mysqld]
bind-address = 0.0.0.0

راهبرد چهارم: تنظیم فایروال

sudo ufw allow from 10.0.0.5 to any port 3306

راهبرد پنجم: بازنشانی سوکت

اگر خطا مربوط به سوکت است:

# بررسی مسیر سوکت
ls -la /var/run/mysqld/
# اگر سوکت وجود ندارد، MySQL را restart کنید

راهبرد ششم: مدیریت اتصال‌ها

در خطای Too many connections:

-- افزایش max_connections
SET GLOBAL max_connections = 300;

-- بررسی اتصال‌های فعال
SHOW PROCESSLIST;

-- کشتن اتصال‌های بی‌استفاده
KILL CONNECTION 123;

نکته‌ی مهم: در محیط production، تغییرات را با احتیاط انجام دهید. هر تغییر، تست شود و در صورت مشکل، rollback انجام دهید. مبانی پشتیبان‌گیری در پشتیبان‌گیری از MySQL آمده است.

این خطا در محیط production

در محیط production، خطای Can't connect ابعاد جدی‌تری دارد:

قطع کامل سرویس

اگر خطای Can't connect رخ دهد، اپلیکیشن نمی‌تواند به دیتابیس متصل شود. این یعنی تمام کاربران، با خطای 500 یا صفحه‌ی سفید مواجه می‌شوند. در فروشگاه‌های آنلاین، این مسئله به از‌دست‌رفتن سفارش‌ها منجر می‌شود.

خطاهای آبشاری

وقتی اتصال به MySQL قطع شود، تمام سرویس‌های وابسته به دیتابیس تحت تأثیر قرار می‌گیرند. در سیستم‌های microservice، این پدیده به cascading failure منجر می‌شود که می‌تواند کل سیستم را از کار بیندازد.

نشت اطلاعات

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

try {
    $conn = new mysqli(...);
} catch (Exception $e) {
    error_log($e->getMessage());
    die("Service temporarily unavailable");
}

پایش و آلارم‌دهی

در production، خطای Can't connect باید به‌طور فوری به تیم فنی اطلاع داده شود. ابزارهایی مثل Sentry، Rollbar، و Nagios این خطاها را جمع‌بندی می‌کنند. نکته: پایش health check دیتابیس باید بخشی از monitoring infrastructure باشد.

پیشگیری با تست

  • Connection tests: تست اتصال در CI.
  • Health checks: پایش مداوم اتصال دیتابیس.
  • Load tests: تست بار با اتصال‌های زیاد.
  • Chaos engineering: تست رفتار سیستم در صورت قطع دیتابیس.

مبانی بهینه‌سازی در بهینه‌سازی کوئری‌های MySQL آمده است.

اشتباهات رایج در برخورد با این خطا

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

اشتباه اول: نادیده گرفتن پیام خطا

پیام MySQL دقیقاً می‌گوید چه اتفاقی افتاده و چه شماره خطایی دارد. نادیده گرفتن این پیام و جستجو در گوگل، زمان دیباگ را چند برابر می‌کند.

اشتباه دوم: نبود مانیتورینگ

اگر uptime دیتابیس پایش نشود، خطاهای اتصال هفته‌ها ادامه می‌یابد بدون اینکه کسی متوجه شود. راه‌حل: پایش مداوم با ابزارهایی مثل UptimeRobot، Pingdom، یا Prometheus.

اشتباه سوم: عدم بررسی فایروال

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

اشتباه چهارم: bind-address = 0.0.0.0 بدون فایروال

این رویکرد، امنیت MySQL را به‌شدت کاهش می‌دهد. راه‌حل: هم bind-address مناسب و هم فایروال سختگیرانه.

اشتباه پنجم: استفاده از localhost در Docker

در Docker، localhost به خود container اشاره می‌کند. راه‌حل: استفاده از نام سرویس.

اشتباه ششم: نبود timeout در اپلیکیشن

اگر اپلیکیشن بدون timeout سعی کند به MySQL متصل شود، کاربران ممکن است دقیقه‌ها منتظر بمانند. راه‌حل: تنظیم timeout مناسب (مثلاً 5 ثانیه).

اشتباه هفتم: عدم مستندسازی پیکربندی

اگر پیکربندی MySQL مستند نشود، در صورت تغییر تیم، بازگردانی سرویس سخت می‌شود. راه‌حل: مستندسازی دقیق پیکربندی، نسخه‌ها و تنظیمات.

خطای Can't connect یک شکایت از مسیر است، نه از هویت. MySQL می‌گوید صدای تو به من نرسید. راه‌حل، بررسی مسیر است نه سرزنش رمز.

پرسش‌های پرتکرار درباره Can't connect to MySQL server

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

تفاوت این خطا با Access denied for user چیست؟

خطای Can't connect در لایه‌ی شبکه رخ می‌دهد و به‌معنای عدم دسترسی به سرویس MySQL است. خطای Access denied در لایه‌ی احراز هویت رخ می‌دهد و به‌معنای رد اعتبار کاربر است. تفاوت: اولی مشکل اتصال، دومی مشکل هویت. جزئیات کامل در خطای Access denied for user در MySQL آمده است.

چرا پیام خطا شماره (111) را نشان می‌دهد؟

شماره 111 در Linux به‌معنای Connection refused است. یعنی سرور MySQL روی آن پورت گوش نمی‌دهد یا اتصال را رد می‌کند. این شماره در macOS و Windows متفاوت است. برای درک دقیق، به مستندات سیستم‌عامل مراجعه کنید.

آیا می‌توانم از root برای حل این خطا استفاده کنم؟

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

چرا localhost و 127.0.0.1 در MySQL متفاوت هستند؟

localhost در MySQL به‌طور پیش‌فرض به Unix socket اشاره می‌کند، در حالی که 127.0.0.1 به TCP/IP اشاره می‌کند. از دید MySQL، این دو مسیر متفاوت هستند. اگر اپلیکیشن با localhost خطا داد، با 127.0.0.1 تست کنید.

آیا MySQL 8 با PHP 7 کار می‌کند؟

بله، ولی ممکن است به‌دلیل تغییر پلاگین احراز هویت پیش‌فرض (caching_sha2_password)، خطا بدهید. راه‌حل: به‌روزرسانی PHP به 7.4+ یا تغییر پلاگین کاربر به mysql_native_password. جزئیات کامل در آموزش PDO در PHP آمده است.

چگونه رمز MySQL را بازنشانی کنم؟

با دستور ALTER USER:

ALTER USER "dbuser"@"localhost" IDENTIFIED BY "new_password";
FLUSH PRIVILEGES;

چرا بعد از تغییر سرور، خطای Can't connect می‌گیرم؟

چون در سرور جدید، پیکربندی MySQL متفاوت است: پورت، bind-address، فایروال و کاربران ممکن است متفاوت باشند. راه‌حل: بررسی همه‌ی این موارد و تطبیق با فایل پیکربندی اپلیکیشن.

آیا خطای Can't connect می‌تواند ناشی از فایروال باشد؟

بله. اگر فایروال جلوی پورت 3306 را بگیرد، اپلیکیشن نمی‌تواند به MySQL متصل شود و خطای Can't connect می‌گیرد. راه‌حل: باز کردن پورت برای IP اپلیکیشن.

چگونه در Docker، این خطا را حل کنم؟

در Docker، اپلیکیشن باید به‌جای localhost، نام سرویس دیتابیس را در DB_HOST استفاده کند:

environment:
  DB_HOST: db
  DB_PORT: 3306

همچنین، اطمینان از اینکه container دیتابیس سالم است:

docker compose ps
docker compose logs db

آیا می‌توانم از telnet برای تست اتصال استفاده کنم؟

بله:

telnet 10.0.0.5 3306

اگر اتصال برقرار شد، پورت باز است. اگر خطای Connection refused گرفتید، MySQL روی آن پورت گوش نمی‌دهد یا فایروال مسدود است.

چرا بعد از restart سرور، MySQL خودکار شروع نمی‌شود؟

چون سرویس MySQL در systemd فعال نیست:

sudo systemctl enable mysql

این دستور، سرویس را در بوت فعال می‌کند.

آیا error 2006 (server has gone away) با error 2002 تفاوت دارد؟

بله. خطای 2002 در زمان اتصال اولیه رخ می‌دهد. خطای 2006 بعد از اتصال، در حین کار، رخ می‌دهد (معمولاً به‌دلیل timeout). راه‌حل 2006: تنظیم wait_timeout و max_allowed_packet.

چگونه در Kubernetes، این خطا را حل کنم؟

در Kubernetes، اپلیکیشن باید از نام سرویس استفاده کند:

env:
  - name: DB_HOST
    value: mysql-service.default.svc.cluster.local
  - name: DB_PORT
    value: "3306"

بررسی سلامت Pod دیتابیس:

kubectl get pods
kubectl logs mysql-pod
kubectl describe pod mysql-pod

آیا Can't connect روی performance تأثیر دارد؟

خود خطا در لحظه‌ی وقوع رخ می‌دهد. اگر اپلیکیشن بدون timeout سعی کند به MySQL متصل شود، کاربران مدت طولانی منتظر می‌مانند که performance را تحت تأثیر قرار می‌دهد. راه‌حل: تنظیم timeout مناسب.

تفاوت (111) و (110) در پیام خطا چیست؟

(111) به‌معنای Connection refused است: سرور MySQL روی آن پورت گوش نمی‌دهد یا اتصال را رد کرده. (110) به‌معنای Connection timed out است: اتصال برقرار نشد و تلاش در زمان مشخص تمام شد. اولی معمولاً مشکل پیکربندی است، دومی معمولاً مشکل فایروال یا شبکه.

چگونه در Django، این خطا را حل کنم؟

در Django، اطلاعات اتصال در settings.py تعریف می‌شود:

DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.mysql",
        "NAME": "mydb",
        "USER": "dbuser",
        "PASSWORD": "password",
        "HOST": "127.0.0.1",  # یا IP سرور
        "PORT": "3306",
        "OPTIONS": {
            "connect_timeout": 5,
        },
    }
}

مبانی کامل در آموزش جنگو برای مبتدیان آمده است.

آیا می‌توانم از mysqladmin برای تست اتصال استفاده کنم؟

بله:

mysqladmin -u dbuser -p -h host ping
# خروجی: mysqld is alive

این دستور سریع‌ترین روش تست اتصال است.

آیا error 2005 (Unknown MySQL server host) متفاوت است؟

بله. این خطا نشان می‌دهد که نام هاست قابل حل نیست. مشکل در DNS است، نه در پیکربندی MySQL یا شبکه. راه‌حل: از IP مستقیم استفاده کنید یا DNS را بررسی کنید.

چرا در محیط production، گاهی اتصال‌ها قطع می‌شوند؟

سه دلیل شایع: اول، timeout پیکربندی MySQL (wait_timeout). دوم، قطع شدن شبکه بین اپلیکیشن و MySQL. سوم، پر شدن ظرفیت اتصال‌ها (max_connections). راه‌حل: مدیریت صحیح connection pool در اپلیکیشن و تنظیم timeout مناسب.

آیا می‌توانم MySQL را روی همان سرور اپلیکیشن و سرور جداگانه همزمان اجرا کنم؟

بله، ولی توصیه نمی‌شود. برای محیط production، جداسازی اپلیکیشن و دیتابیس توصیه می‌شود. این رویکرد، مقیاس‌پذیری و مدیریت مستقل را فراهم می‌کند.

چگونه در macOS، MySQL را restart کنم؟

brew services restart mysql
# یا
brew services start mysql

آیا error 1045 هم در همین دسته است؟

نه. خطای 1045 (Access denied) در لایه‌ی احراز هویت رخ می‌دهد، نه در لایه‌ی اتصال. تفاوت: Can't connect مشکل شبکه، Access denied مشکل هویت. جزئیات کامل در خطای Access denied for user در MySQL آمده است.

چگونه از این خطا در CI/CD جلوگیری کنم؟

سه رویکرد: اول، health check قبل از اجرای تست‌ها. دوم، انتظار برای آمادگی سرویس MySQL با دستور wait-for-it یا مشابه. سوم، retry منطقی در صورت خطای اتصال.

آنچه از سال‌ها کار با اتصال MySQL آموختم

اگر بخواهم چکیده‌ی این سال‌ها را در چند جمله بگویم، سه اصل عملی دارم:

یک: خطای Can't connect یک سیگنال از مسیر است، نه از اپلیکیشن. هر بار که این خطا می‌بینید، به‌جای سرزنش کد، به مسیر شبکه، پیکربندی MySQL، و فایروال نگاه کنید. در ۹۰ درصد موارد، ریشه در یکی از این سه است.

دو: پایش uptime دیتابیس، بیمه‌ی production است. پایش مداوم با ابزارهایی مثل UptimeRobot یا Prometheus، امکان کشف سریع خطاها را فراهم می‌کند. بدون پایش، خطاها ممکن است هفته‌ها ادامه یابند.

سه: مستندسازی پیکربندی، سرمایه‌گذاری بلندمدت است. پیکربندی MySQL، هاست، پورت، و تنظیمات فایروال را مستند کنید. این رویکرد، در صورت تغییر تیم یا بازیابی سرویس، زمان دیباگ را چند برابر کاهش می‌دهد.

در کنار این سه اصل، یک هشدار عملی هم دارم: خطای Can't connect در نگاه اول یک مشکل ساده به‌نظر می‌رسد، ولی وقتی در چارچوب کلی معماری سیستم دیده شود، تبدیل به یک سیگنال می‌شود. این سیگنال می‌گوید که لایه‌ی اتصال دیتابیس نیاز به بازنگری دارد. اگر این سیگنال را جدی بگیرید و ساختار اتصال را تقویت کنید، پروژه‌ی شما در ماه‌های بعد پایدارتر خواهد بود.

هدف این مقاله، تمام‌کردن همه‌ی سناریوهای ممکن نبود. هدف، دادن یک چارچوب ذهنی برای تشخیص، پیشگیری و رفع این خطا بود. وقتی این چارچوب را درونی کنید، برخورد با خطای Can't connect از یک واکنش اضطراری به یک فرآیند منظم تبدیل می‌شود.

اگر خطای Can't connect در پروژه‌ی شما به شکلی ظاهر شده که با الگوهای این مقاله حل نشده، برای من جالب است بدانم کدام سناریو بود. تجربه‌ی خودتان را در دیدگاه‌ها بنویسید؛ به‌ویژه اگر راه‌حلی پیدا کرده‌اید که هنوز در این مقاله نیست. 🔌