结论: 数据库连接失败要先按错误码分类,再对症下药:2003/2002 是连不上(网络或 socket 路径问题)、1045 是账号密码或认证插件错误、1130 是主机不允许、1040 是连接数打满、1049 是库不存在。统一排查顺序为:端口通不通 → 凭据对不对 → 权限够不够 → 资源满没满。套用这个顺序,90% 的数据库连接失败在 5 分钟内可定位。
很多教程只讲"连接超时",但真实的报错远不止超时一种。应用日志里那些让人一头雾水的 Communications link failure、connection refused、Access denied,其实每一条都对应一个明确的错误码和明确的成因。本文以 MySQL 8.4 为主(截至 2026 年的主流版本),并覆盖 PostgreSQL 与 Redis 的对应场景。
数据库连接失败错误码分类表
结论:先看错误码,错误码直接决定排查方向。 下表可直接对照使用。
| 错误码 | 报错文本(英文) | 中文含义 | 首查方向 |
|---|---|---|---|
| 2003 (CR_CONN_HOST_ERROR) | Can't connect to MySQL server on 'x.x.x.x:3306' | TCP 连不上 | 端口/防火墙/bind-address |
| 2002 (CR_CONNECTION_ERROR) | Can't connect to local server through socket | 本地 socket 连不上 | socket 路径、mysqld 是否运行 |
| 2005 | Unknown MySQL server host | 域名解析失败 | DNS / hosts |
| 2013 | Lost connection to MySQL server during query | 查询中断连接 | 超时、max_allowed_packet、网络抖动 |
| 1040 | Too many connections | 连接数已满 | max_connections、连接池泄漏 |
| 1044 | Access denied for user to database | 无该库权限 | 授权 GRANT |
| 1045 | Access denied for user 'u'@'h' (using password: YES) | 账号/密码/认证插件错 | 凭据、plugin、host 匹配 |
| 1049 | Unknown database 'xxx' | 库不存在 | 库名拼写、大小写 |
| 1129 | Host 'x' is blocked because of many connection errors | 该主机被拉黑 | FLUSH HOSTS、调 max_connect_errors |
| 1130 | Host 'x' is not allowed to connect to this MySQL server | 主机不允许 | user 表的 host 字段 |
| 2026 | SSL connection error | SSL 握手失败 | 证书、require_secure_transport |
PostgreSQL 与 Redis 的对应关系:
| 组件 | 典型报错 | 首查方向 |
|---|---|---|
| PostgreSQL | could not connect to server: Connection refused | listen_addresses、端口 5432 |
| PostgreSQL | no pg_hba.conf entry for host | pg_hba.conf 未放行客户端网段 |
| PostgreSQL | password authentication failed for user | 密码或 scram-sha-256 配置 |
| Redis | DENIED Redis is running in protected mode | protected-mode、bind |
| Redis | NOAUTH Authentication required | requirepass 未配置到客户端 |
| Redis | ERR max number of clients reached | maxclients 与连接泄漏 |
第一类:完全连不上(2003 / 2002)
结论:先用 nc 验证端口可达性,端口不通就是网络层问题,与账号无关。
这是最基础的一层。常见成因:MySQL 监听地址配错(bind-address=127.0.0.1 导致远程连不上)、服务没启动、防火墙/安全组未放行 3306、云数据库的白名单没加客户端 IP。
# 客户端侧:TCP 端口是否可达
nc -zv -w 3 10.0.0.20 3306
telnet 10.0.0.20 3306
# 服务端侧:mysqld 是否在跑、监听在哪个地址
sudo systemctl status mysql
sudo ss -lntup 'sport = :3306'
# 关键看 Local Address:0.0.0.0:3306(全部) / 127.0.0.1:3306(仅本地) / :::3306
# 查看 MySQL 实际监听配置
mysql -uroot -p -e "SHOW VARIABLES LIKE 'bind_address'; SHOW VARIABLES LIKE 'skip_networking'; SHOW VARIABLES LIKE 'port';"
# 本地 socket 路径
mysql -uroot -p -e "SHOW VARIABLES LIKE 'socket';"
mysql --protocol=SOCKET -S /run/mysqld/mysqld.sock -uroot -p修正监听配置:
# /etc/mysql/mysql.conf.d/mysqld.cnf (RHEL 系为 /etc/my.cnf.d/mysql-server.cnf)
[mysqld]
bind-address = 0.0.0.0 # 允许远程;仅本地应用则保持 127.0.0.1 更安全
port = 3306
# skip-networking 必须为注释状态,否则只能走 socketsudo systemctl restart mysql
sudo ss -lntup 'sport = :3306'安全提示:开放 0.0.0.0 后必须同步配置安全组/防火墙,仅允许应用服务器 IP 访问 3306,绝不对公网开放数据库端口。
第二类:账号密码与认证插件错误(1045)
结论:MySQL 8 默认认证插件是 caching_sha2_password,老客户端(如 Python 的旧版 MySQLdb、PHP 5.x)不支持它,会表现成"密码正确却连不上"。
排查顺序:确认用户名 → 确认 host 匹配 → 确认认证插件 → 确认密码。
-- 查看账号的 host 与认证插件
SELECT user, host, plugin, account_locked FROM mysql.user WHERE user='appuser';
-- 测试用:创建允许指定网段连接的账号
CREATE USER 'appuser'@'10.0.0.%' IDENTIFIED BY 'Str0ng-Passw0rd';
GRANT SELECT,INSERT,UPDATE,DELETE ON shopdb.* TO 'appuser'@'10.0.0.%';
FLUSH PRIVILEGES;
-- 兼容老客户端:改用 mysql_native_password
ALTER USER 'appuser'@'10.0.0.%' IDENTIFIED WITH mysql_native_password BY 'Str0ng-Passw0rd';
FLUSH PRIVILEGES;# 在应用服务器上用命令行复现,最能说明问题
mysql -h 10.0.0.20 -P 3306 -u appuser -p -e "SELECT 1"
# 检查账号是否被锁定 / 密码是否过期
mysql -uroot -p -e "SELECT user,host,account_locked,password_expired FROM mysql.user;"MySQL 的账号是 'user'@'host' 二元组,'appuser'@'localhost' 和 'appuser'@'10.0.0.%' 是两个完全不同的账号。定义了 localhost 却从远程连,就会命中 1130 Host is not allowed。
第三类:主机不允许与被拉黑(1130 / 1129)
结论:1130 是授权表里没有匹配的 host 记录;1129 是同一客户端连续错误次数超过 max_connect_errors 被临时拉黑。
-- 1130:补授权,host 用网段通配
CREATE USER 'appuser'@'10.0.0.%' IDENTIFIED BY 'Str0ng-Passw0rd';
GRANT ALL PRIVILEGES ON shopdb.* TO 'appuser'@'10.0.0.%';
FLUSH PRIVILEGES;
-- 1129:解除拉黑
FLUSH HOSTS;
-- 或调大阈值
SET GLOBAL max_connect_errors = 10000;# 永久生效
[mysqld]
max_connect_errors = 10000注意:1129 往往是被"应用配置了错误密码却不停重试"触发的。解封之后必须同时修正应用配置,否则几分钟后又会拉黑。
第四类:连接数打满(1040 Too many connections)
结论:1040 分成"真不够用"和"连接池泄漏"两种,先查 Threads_connected 与连接存活时间来判断。
MySQL 默认 max_connections = 151,稍微有点并发就不够。
-- 当前连接数与上限
SHOW VARIABLES LIKE 'max_connections';
SHOW STATUS LIKE 'Threads_connected';
SHOW STATUS LIKE 'Max_used_connections';
-- 谁在占连接、睡了多久(Sleep 很久的说明应用没释放)
SELECT id, user, host, db, command, time, state, LEFT(info,60) AS q
FROM information_schema.PROCESSLIST
ORDER BY time DESC LIMIT 20;
-- 紧急临时扩容(重启失效)
SET GLOBAL max_connections = 1000;# 永久配置
[mysqld]
max_connections = 1000
wait_timeout = 60 # 非交互连接空闲 60 秒断开
interactive_timeout = 120根治在于连接池(Connection Pool)配置,以 Java 的 HikariCP 为例:
# application.properties
spring.datasource.hikari.maximum-pool-size=20
spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.idle-timeout=600000
spring.datasource.hikari.max-lifetime=1800000
spring.datasource.hikari.connection-timeout=30000
spring.datasource.hikari.leak-detection-threshold=60000经验公式:maximum-pool-size ≈ CPU核数 * 2 + 磁盘数,且多台应用实例的总连接数不能超过数据库 max_connections 的 80%。4 台应用各配 20,就是 80 个连接,1000 的上限绰绰有余;但如果配成 300,4 台就打满了。
leak-detection-threshold=60000(60 秒)能直接打出未关闭连接的堆栈,是排查泄漏最有效的开关。
第五类:库不存在与 SSL 要求(1049 / 2026)
结论:1049 多为拼写或大小写问题;2026 是服务端强制要求 TLS 而客户端未启用。
-- 1049:列出真实库名,注意 Linux 上库名默认区分大小写
SHOW DATABASES;
SELECT @@lower_case_table_names; # 0=区分大小写(Linux 默认)# 服务端强制 TLS
[mysqld]
require_secure_transport = ON# 客户端必须显式启用 SSL
mysql -h 10.0.0.20 -u appuser -p --ssl-mode=REQUIRED -e "STATUS" | grep -i ssl# JDBC 连接串启用 TLS
spring.datasource.url=jdbc:mysql://10.0.0.20:3306/shopdb?useSSL=true&requireSSL=true&serverTimezone=Asia/Shanghai通用排查顺序:五步定位法
结论:按"端口 → 凭据 → 权限 → 资源 → 日志"五步走,每一步都有明确命令,不靠猜。
# 步骤 1:网络层——端口可达吗?
nc -zv -w 3
# 步骤 2:服务层——数据库活着吗?
mysqladmin -h -u root -p ping
systemctl status mysql
# 步骤 3:凭据层——命令行用同样的账号密码能连吗?
mysql -h -u -p -e "SELECT CURRENT_USER(), @@hostname"
# 步骤 4:资源层——连接数、磁盘、内存是否打满
mysql -uroot -p -e "SHOW STATUS LIKE 'Threads_connected'; SHOW VARIABLES LIKE 'max_connections';"
df -h /var/lib/mysql
# 步骤 5:日志层——看服务端错误日志,它通常直接写了拒绝原因
sudo tail -n 100 /var/log/mysql/error.log
sudo journalctl -u mysql -n 100 --no-pager 一个容易忽略的点:磁盘满了也会导致连接失败。MySQL 需要写临时文件和 binlog,数据目录 100% 时会出现各种诡异报错。df -h /var/lib/mysql 应当成为固定检查项。
企业QQ咨询




