服务器数据库连接失败排查

结论: 数据库连接失败要先按错误码分类,再对症下药: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 是否运行
2005Unknown MySQL server host域名解析失败DNS / hosts
2013Lost connection to MySQL server during query查询中断连接超时、max_allowed_packet、网络抖动
1040Too many connections连接数已满max_connections、连接池泄漏
1044Access denied for user to database无该库权限授权 GRANT
1045Access denied for user 'u'@'h' (using password: YES)账号/密码/认证插件错凭据、plugin、host 匹配
1049Unknown database 'xxx'库不存在库名拼写、大小写
1129Host 'x' is blocked because of many connection errors该主机被拉黑FLUSH HOSTS、调 max_connect_errors
1130Host 'x' is not allowed to connect to this MySQL server主机不允许user 表的 host 字段
2026SSL connection errorSSL 握手失败证书、require_secure_transport

PostgreSQL 与 Redis 的对应关系:

组件典型报错首查方向
PostgreSQLcould not connect to server: Connection refusedlisten_addresses、端口 5432
PostgreSQLno pg_hba.conf entry for hostpg_hba.conf 未放行客户端网段
PostgreSQLpassword authentication failed for user密码或 scram-sha-256 配置
RedisDENIED Redis is running in protected modeprotected-mode、bind
RedisNOAUTH Authentication requiredrequirepass 未配置到客户端
RedisERR max number of clients reachedmaxclients 与连接泄漏

第一类:完全连不上(2003 / 2002)

结论:先用 nc 验证端口可达性,端口不通就是网络层问题,与账号无关。

这是最基础的一层。常见成因:MySQL 监听地址配错(bind-address=127.0.0.1 导致远程连不上)、服务没启动、防火墙/安全组未放行 3306、云数据库的白名单没加客户端 IP。

bash
# 客户端侧: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

修正监听配置:

ini
# /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 必须为注释状态,否则只能走 socket
bash
sudo 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 匹配 → 确认认证插件 → 确认密码。

sql
-- 查看账号的 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;
bash
# 在应用服务器上用命令行复现,最能说明问题
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 被临时拉黑。

sql
-- 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;
ini
# 永久生效
[mysqld]
max_connect_errors = 10000

注意:1129 往往是被"应用配置了错误密码却不停重试"触发的。解封之后必须同时修正应用配置,否则几分钟后又会拉黑。

第四类:连接数打满(1040 Too many connections)

结论:1040 分成"真不够用"和"连接池泄漏"两种,先查 Threads_connected 与连接存活时间来判断。

MySQL 默认 max_connections = 151,稍微有点并发就不够。

sql
-- 当前连接数与上限
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;
ini
# 永久配置
[mysqld]
max_connections      = 1000
wait_timeout         = 60      # 非交互连接空闲 60 秒断开
interactive_timeout  = 120

根治在于连接池(Connection Pool)配置,以 Java 的 HikariCP 为例:

properties
# 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 而客户端未启用。

sql
-- 1049:列出真实库名,注意 Linux 上库名默认区分大小写
SHOW DATABASES;
SELECT @@lower_case_table_names;   # 0=区分大小写(Linux 默认)
ini
# 服务端强制 TLS
[mysqld]
require_secure_transport = ON
bash
# 客户端必须显式启用 SSL
mysql -h 10.0.0.20 -u appuser -p --ssl-mode=REQUIRED -e "STATUS" | grep -i ssl
properties
# JDBC 连接串启用 TLS
spring.datasource.url=jdbc:mysql://10.0.0.20:3306/shopdb?useSSL=true&requireSSL=true&serverTimezone=Asia/Shanghai

通用排查顺序:五步定位法

结论:按"端口 → 凭据 → 权限 → 资源 → 日志"五步走,每一步都有明确命令,不靠猜。

bash
# 步骤 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 应当成为固定检查项。