如何修复 MySQL Connection Refused 错误

当你的应用尝试连接数据库的那一刻,MySQL 的 "connection refused"(连接被拒绝)错误会让它瞬间停止。无论你看到的是 Can't connect to MySQL serverERROR 2003 (HY000),还是某个连接库抛出的通用 Connection refused,含义都一样:客户端向它认为 MySQL 运行所在的主机和端口发送了 TCP 数据包,而远端的操作系统主动拒绝了它——那个端口上没有任何进程在应答。

好消息是,"被拒绝"是一个精确的信号。它告诉你网络路径基本是通的——主机可达、端口关闭、拒绝在毫秒内返回。这让问题缩小到一小撮可预测的原因:mysqld 进程没有运行、它监听了错误的端口或接口、防火墙阻断了 3306 端口、用户没有从该主机连接的权限,或者在容器化环境里 Docker 网络或端口映射配置有误。下面六个步骤按从简单到隐蔽的顺序逐一排查。

MySQL 中的 'Connection Refused' 是什么意思?

在 MySQL 中,connection refused 是传输层的失败。TCP 握手从未完成,因为目标端口(默认 3306)上没有进程在接收连接。客户端根本到不了 MySQL 的认证阶段,所以数据库没机会校验用户名或密码。错误几乎是瞬时返回的——这是最关键的诊断线索。

它与 access denied(访问被拒绝)有本质区别,后者是应用层的失败。access denied 时 TCP 连接成功建立、MySQL 接受了它,然后才拒绝凭证——产生 ERROR 1045 (28000): Access denied for user。两类错误在应用日志里看起来很相似,但根因和修法完全不同。被拒绝意味着"去修服务器、端口或防火墙";access denied 意味着"去修用户名、密码或允许该用户连接的主机"。

还有第三个相关信号值得了解:超时。如果客户端等待许多秒后放弃且没有任何响应,说明主机不可达或防火墙悄悄丢弃了数据包。被拒绝是快的;超时是慢且无声的。先判断你遇到的是哪一种,才知道该先看哪里。

常见原因

原因 错误信息 修复方法
mysqld 未运行 Can't connect to MySQL server (2003) systemctl start mysql
客户端配置端口错误 非 3306 端口上 Connection refused 修正连接串中的端口
bind-address = 127.0.0.1 远程被拒绝,本地可连 设置 bind-address = 0.0.0.0
防火墙阻断 3306 Connection refused 或超时 在 ufw/firewalld/安全组中放行 3306
用户不允许从该主机连接 ERROR 1130 (HY000): Host not allowed GRANT ... TO 'user'@'%'
Docker 端口未发布 从宿主机到容器被拒绝 在 run/compose 中映射 -p 3306:3306

分步修复指南

步骤 1:检查 mysqld 是否在运行

最常见的原因是 MySQL 守护进程根本没有运行。mysqld 崩溃、重启失败,或者某次包更新停掉了服务,都会导致瞬时拒绝。先检查服务状态:

# Debian / Ubuntu
sudo systemctl status mysql

# RHEL / CentOS / Fedora
sudo systemctl status mysqld

# MariaDB
sudo systemctl status mariadb

如果状态显示 inactivefaileddead,启动服务并设为开机自启:

sudo systemctl start mysql
sudo systemctl enable mysql

# 如果启动失败,查看日志找出真正原因
sudo journalctl -u mysql -n 50 --no-pager

日志里常见的崩溃原因包括磁盘已满(No space left on device)、ibdata1 文件损坏,以及端口冲突——另一个进程已经占用了 3306。先修复根本原因再重启,否则 mysqld 会再次退出。

步骤 2:验证 MySQL 监听的端口

如果 mysqld 报告 active (running) 但仍然被拒绝,确认它确实监听在你客户端期望的端口上。MySQL 可能配置了非默认端口,或只绑定了回环接口。

# 使用 ss(现代工具,推荐)
sudo ss -tlnp | grep mysql

# 使用 netstat(旧版)
sudo netstat -tlnp | grep mysql

仔细阅读 Local Address:Port 列:

LISTEN  0  151  127.0.0.1:3306  0.0.0.0:*  users:(("mysqld",pid=1234,fd=21))
LISTEN  0  151  0.0.0.0:3306     0.0.0.0:*  users:(("mysqld",pid=1234,fd=22))

127.0.0.1:3306 表示 MySQL 只接受本机连接——任何远程客户端都会被拒绝。0.0.0.0:3306 表示它在所有接口上接受连接。如果端口不是 3306,你的客户端连接串必须与之匹配(例如 --port=3307jdbc:mysql://host:3307/db)。

步骤 3:检查 my.cnf/my.ini 中的 bind-address

监听接口由 MySQL 配置文件中的 bind-address 指令控制。许多发行版出于安全考虑默认把 MySQL 绑定到 127.0.0.1,这正是即使服务器健康、远程连接仍被拒绝的原因。

# Linux 配置位置
sudo grep -R bind-address /etc/mysql/
# 典型文件: /etc/mysql/mysql.conf.d/mysqld.cnf

# Windows 配置位置
# C:\ProgramData\MySQL\MySQL Server 8.0\my.ini

打开文件,定位到 [mysqld] 段。要允许远程连接,把绑定地址改为所有接口:

[mysqld]
bind-address = 0.0.0.0
# 可选: 限定到某个接口或 IP
# bind-address = 192.168.1.50

保存后重启 MySQL 使更改生效:

sudo systemctl restart mysql

注意,绑定到 0.0.0.0 会把 MySQL 暴露到主机所连接的每一个网络。只在防火墙之后这么做,并配合强密码和最小权限用户设置(见步骤 5)。

步骤 4:配置防火墙规则

即便 MySQL 监听在 0.0.0.0:3306,主机防火墙或云安全组仍然可能拒绝或丢弃连接。用服务器上启用的防火墙检查并放行 3306 端口:

# ufw(Ubuntu / Debian)
sudo ufw status
sudo ufw allow 3306/tcp

# firewalld(RHEL / CentOS)
sudo firewall-cmd --permanent --add-port=3306/tcp
sudo firewall-cmd --reload

# iptables(任意发行版)
sudo iptables -A INPUT -p tcp --dport 3306 -j ACCEPT
sudo iptables -L -n | grep 3306

不要忘了云这一层。AWS 安全组、Azure 网络安全组和 Google Cloud 防火墙规则独立于操作系统防火墙运作——那里一条关闭的 3306 入站规则会产生一个和本地拦截看起来一模一样的瞬时拒绝。尽可能针对客户端 IP 范围添加一条 3306 的 TCP 入站规则,而不是对 0.0.0.0/0 全部放行。

步骤 5:检查 MySQL 用户权限和主机

如果 TCP 连接现在成功了,但你在 MySQL 层仍被拒,可能是连接用户没有被允许从客户端主机连接。MySQL 会同时匹配用户名和存储在 mysql.user 中的来源主机。定义为 'app'@'localhost' 的用户无法从远程 IP 连接,MySQL 会返回 ERROR 1130 (HY000): Host 'x.x.x.x' is not allowed to connect to this MySQL server

以 root 在本地登录并检查允许的主机:

SELECT User, Host FROM mysql.user WHERE User = 'appuser';

-- 允许该用户从任意主机连接(谨慎使用)
CREATE USER IF NOT EXISTS 'appuser'@'%' IDENTIFIED BY 'strong-password';
GRANT ALL PRIVILEGES ON appdb.* TO 'appuser'@'%';
FLUSH PRIVILEGES;

在生产环境,优先使用具体主机或子网而非 %——例如 'appuser'@'10.0.0.%'——以在凭证泄露时缩小影响范围。修改授权后,运行 FLUSH PRIVILEGES;(或依赖 MySQL 8.0+ 的自动重载),然后重试连接。

步骤 6:使用 mysql CLI 和 telnet/nc 测试连接

在归咎于应用框架之前,先用最简单的工具复现故障。首先,用官方 mysql 客户端按应用使用的完全相同的主机、端口和凭证连接:

mysql -h 192.168.1.50 -P 3306 -u appuser -p appdb

如果这步失败,错误信息现在很精确(2003、1045 或 1130),会把你指回上面对应的步骤。如果 mysql 客户端成功但应用仍失败,问题出在应用的连接串或驱动,而不是 MySQL 本身。

为了把网络和数据库分开排查,用 telnetnc 直接测试原始 TCP 端口。这些工具完全绕过 MySQL——连接成功证明端口开放;被拒绝证明没有:

# telnet
telnet 192.168.1.50 3306

# netcat
nc -zv 192.168.1.50 3306

健康的 MySQL 端口会返回一段乱码的握手串(MySQL 问候);关闭的端口会立即返回 Connection refused。这种双工具方法能干净地把网络问题与数据库问题分开。

Docker MySQL 连接修复

Docker 里的 MySQL 多了两层经常导致拒绝的原因:容器网络和端口发布。默认情况下,容器的端口不会对宿主机可达,除非你显式映射。

运行容器时发布端口,让宿主机(以及其他机器)能访问 MySQL:

docker run -d \
  --name mysql \
  -p 3306:3306 \
  -e MYSQL_ROOT_PASSWORD=secret \
  mysql:8.0

使用 docker-compose 时,定义端口映射并创建命名网络,让其他服务能按服务名访问 MySQL:

services:
  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: secret
      MYSQL_DATABASE: appdb
    ports:
      - "3306:3306"
    networks:
      - appnet

  app:
    image: myapp:latest
    depends_on:
      - db
    environment:
      DB_HOST: db        # 按服务名连接,不是 localhost
      DB_PORT: "3306"
    networks:
      - appnet

networks:
  appnet:

Docker 下两个常见陷阱导致了大多数拒绝。第一,运行在宿主机上的应用连接 127.0.0.1:3306,但运行在另一个容器里的应用必须连接服务名 db(或其容器 IP)——容器里的 localhost 指的是该容器自己,不是数据库。第二,官方镜像里 MySQL 的 bind-address 默认是 *,但作为卷挂载的自定义 my.cnf 可能把覆盖回了 127.0.0.1,重新引入拒绝。用 docker exec -it mysql mysql -e "SHOW VARIABLES LIKE 'bind_address'" 检查生效配置。

配置示例

一份正确、可远程访问、位于防火墙之后的 Linux 服务器 my.cnf

[mysqld]
# 监听所有接口,让远程客户端能到达服务器
bind-address = 0.0.0.0

# 默认端口;只有同时更新客户端和防火墙时才修改
port = 3306

# 连接数上限;如果遇到 "Too many connections" 则调高
max_connections = 200

# 远程会话要求 TLS(推荐)
require_secure_transport = ON

对应的 SQL:创建一个最小权限远程用户并应用:

-- 创建只能从特定子网连接的用户
CREATE USER 'appuser'@'10.0.0.0/255.255.255.0'
  IDENTIFIED BY 'a-long-random-password';

-- 只授予应用在单个数据库上所需的权限
GRANT SELECT, INSERT, UPDATE, DELETE ON appdb.* TO 'appuser'@'10.0.0.0/255.255.255.0';

-- 应用更改
FLUSH PRIVILEGES;

-- 验证用户和主机
SELECT User, Host FROM mysql.user WHERE User = 'appuser';

快速参考表

错误码 信息 含义
2002 Can't connect to local MySQL server through socket socket 路径错误或本地 mysqld 未运行
2003 Can't connect to MySQL server on 'host' (111) 连接被拒绝——端口关闭、服务停止或防火墙
1045 Access denied for user TCP 已连接;用户名、密码错误或主机未授权
1130 Host 'x.x.x.x' is not allowed to connect 用户存在但不允许从该主机连接——修正 GRANT 的主机
1698 Access denied for user 'root'@'localhost' Ubuntu auth_socket 插件;用 sudo mysql 或切换插件

常见问题

为什么我遇到 "connection refused",而同事却能正常连接?

差别几乎总在来源。你的同事可能处于防火墙允许的网络或 IP 上,而你的被拦截;或者他的 MySQL 用户的 Host 值更宽松(如 %),而你的被限制在 localhost。检查云安全组、主机防火墙,以及针对你具体 IP 的 mysql.user host 列。

"connection refused" 和超时是一回事吗?

不是。被拒绝会立即返回,因为目标端口关闭或没有进程监听。超时则是客户端等待无果后慢慢返回,通常意味着主机不可达或防火墙在悄悄丢包而非拒绝。失败的速度能告诉你遇到的是哪一类问题。

生产环境应该把 bind-address 设为 0.0.0.0 吗?

只在防火墙之后这么做,且最好限定到私有子网。绑定到 0.0.0.0 会把 MySQL 暴露到主机的每一个接口。配合 3306 端口的严格防火墙规则、强密码、TLS(require_secure_transport=ON),以及限定到具体 IP 范围而非 % 的最小权限授权一起使用。

为什么我的 Docker 应用用 localhost 能连,从另一个容器却失败?

在容器里,localhost 指的是该容器自己,不是数据库。从宿主机你可以用 127.0.0.1:3306(通过 -p 映射),但从同一 Docker 网络里的另一个容器连接时,必须使用数据库服务名(例如 db)或其容器 IP。把两个服务放到同一命名网络上,并在连接串里引用服务名。

总结

MySQL 的 "connection refused" 错误令人头疼但范围很窄:它意味着到数据库的 TCP 连接从未完成,因为预期端口上没有任何东西接受它。按顺序走完六个步骤——确认 mysqld 在运行、验证监听端口和接口、修正 bind-address、开放防火墙、检查用户主机权限,并用 mysql 客户端和 telnet/nc 复现故障。对于 Docker,再把端口发布和服务名网络加到清单上。

最有价值的习惯是把传输层和认证层分开。如果失败是瞬时的,那是网络或服务问题;如果是 ERROR 10451130,网络没问题,修复在于你的授权。用错误码而非消息措辞来决定往哪看——你就能在几分钟而非几小时内解决绝大多数 MySQL 连接拒绝问题。

相关指南