如何修复 MySQL Connection Refused 错误
当你的应用尝试连接数据库的那一刻,MySQL 的 "connection refused"(连接被拒绝)错误会让它瞬间停止。无论你看到的是 Can't connect to MySQL server、ERROR 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
如果状态显示 inactive、failed 或 dead,启动服务并设为开机自启:
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=3307 或 jdbc: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 本身。
为了把网络和数据库分开排查,用 telnet 或 nc 直接测试原始 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 1045 或 1130,网络没问题,修复在于你的授权。用错误码而非消息措辞来决定往哪看——你就能在几分钟而非几小时内解决绝大多数 MySQL 连接拒绝问题。