WordPress 数据库连接错误:从报错原文到修复
打开站点只看到一行 Error establishing a database connection,中文语言包会显示「建立连接数据库时出错」。这条信息故意不透露细节,因为 WordPress 不想把数据库地址和账号错误暴露给访客。但对站长来说,它把所有可能性压缩成了一句,所以必须自己一层层往下挖。
三种原文,指向的不是一件事
同样看起来是「连不上」,报错出现在不同阶段,原因完全不同。先在浏览器里确认看到的是哪一句,别凭印象判断。
| 报错原文 | 失败在哪一步 | 大概率原因 |
|---|---|---|
| Error establishing a database connection | 握手阶段,连接就没建立 | 库地址错、账号密码错、MySQL 没启动、端口不通 |
| Error selecting database | 连上了,选库失败 | DB_NAME 写错,或该用户对库没有权限 |
| 建立连接数据库时出错 | 同第一行(中文语言包) | 同上 |
| Table ‘./db/wp_options’ is marked as crashed and should be repaired | 连上了,读表失败 | 表损坏,需要 REPAIR TABLE |
| MySQL server has gone away | 连接中途断开 | 包太大、超时、MySQL 被 OOM 杀掉 |
区分第一种和第二种很关键:第一种说明问题在网络或凭据,第二种说明凭据是对的但库名或权限不对,两者的排查路径完全相反。
逐项核对四个常量
WordPress 的数据库信息全在 wp-config.php 里。这个文件如果被改过(换服务器、改密码、装迁移插件),四个常量任何一个写错都会导致同一个报错。
define( 'DB_NAME', 'wp_db' );
define( 'DB_USER', 'wpuser' );
define( 'DB_PASSWORD', 'p@ss&word#2026' );
define( 'DB_HOST', 'localhost' );
define( 'DB_CHARSET', 'utf8mb4' );
define( 'DB_COLLATE', '' );
几个真实踩过的坑。密码里有单引号时没有转义,PHP 直接语法错误,页面可能白屏而不是报数据库错;密码首尾被复制进了空格,肉眼看不出来,连不上;DB_HOST 写成 localhost:3306,WordPress 会把它当主机名传给 mysqli,解析失败;库名在 phpMyAdmin 里看到的是 wp_db,但主机面板给用户授权的其实是 wp_db_prod,这种在共享主机上很常见。
核对方式不要靠眼睛。登录 phpMyAdmin 或命令行,用同一组账号手工连一次:mysql -h 主机 -u 用户 -p 库名。能进去,说明凭据和权限没问题,问题在 PHP 侧或网络侧;进不去,报错会直接告诉你 Access denied 还是 Unknown database。
用最小脚本拿到真实报错
WordPress 把错误吞掉了,但 PHP 本身不会。在站点根目录临时放一个测试文件,绕开 WordPress 直接连库,就能看到 mysqli 的原始错误。测完立刻删掉。
<?php
$m = new mysqli( 'localhost', 'wpuser', 'password', 'wp_db' );
if ( $m->connect_error ) {
die( 'connect: ' . $m->connect_error );
}
echo 'connected, server version: ' . $m->server_info;
$m->close();
常见的三种返回对应三个方向:Access denied for user 是账号密码或授权主机不对;Unknown database 是库名错或库不存在;Can't connect to local MySQL server through socket 是 socket 路径问题或服务没起;Connection refused 是 TCP 端口不通。这一步能省掉后面 80% 的猜测。
localhost 和 127.0.0.1 不是一回事
这是最容易忽略的一条。两个写法看起来等价,走的路径完全不同。
| 写法 | 实际走什么 | 什么时候出问题 |
|---|---|---|
localhost |
Unix socket 文件 | PHP 编译时的 socket 路径和 MySQL 实际路径不一致 |
127.0.0.1 |
TCP 回环,端口 3306 | MySQL 只监听 socket、没监听 TCP,或防火墙拦了回环 |
| 内网 IP / 主机名 | TCP,走网卡 | 数据库在另一台机器,安全组没放行 |
典型场景:迁移到新服务器后,PHP 的 mysqli.default_socket 指向 /tmp/mysql.sock,而 MySQL 实际把 socket 放在 /var/run/mysqld/mysqld.sock,用 localhost 就连不上,报 No such file or directory。把 DB_HOST 改成 127.0.0.1 立刻正常,因为绕开了 socket。
反过来也有坑:某些环境为了性能禁用了 TCP 监听(skip-networking),这时改 127.0.0.1 会变成 Connection refused,必须用 socket。判断方法很简单,ss -lntp | grep 3306 有输出就是监听了 TCP,没有就只能走 socket。用 SHOW VARIABLES LIKE 'socket'; 能看到真实路径。
确认数据库服务本身活着
前两步都通过、报错依旧是连接失败时,问题在服务端。按顺序看三样东西:
- 服务状态:
systemctl status mysqld(Debian/Ubuntu 上是mysql)。状态是 failed 就去看日志,不要急着重启,重启前先确认能不能救出数据。 - 端口监听:
ss -lntp | grep 3306,没有输出说明服务起来了但没监听,通常是配置被改坏。 - 磁盘空间:
df -h。磁盘写满时 MySQL 会直接退出或者拒绝写入,日志里是No space left on device。这是最容易被忽略的原因,尤其是 binlog 没清理的服务器,/var/lib/mysql能悄悄涨到几十 G。
日志位置:CentOS 系在 /var/log/mysqld.log,Debian 系在 /var/log/mysql/error.log,宝塔面板在 /www/server/data/*.err。看最后 50 行基本就能定位。如果服务起不来又急,先把数据目录整个复制一份再动手,别在唯一的副本上做修复操作。
表损坏:REPAIR TABLE 的适用范围
站点能连上库,但某些页面 500,或者后台提示 wp_options 崩溃,属于另一类问题。先检查再修复,不要上来就 REPAIR。
CHECK TABLE wp_posts, wp_options, wp_postmeta, wp_term_relationships;
REPAIR TABLE wp_posts;
要注意 REPAIR TABLE 只对 MyISAM 表有效。现代 WordPress 默认用 InnoDB,对 InnoDB 跑 REPAIR 通常返回「doesn’t support repair」,这不是失败,而是根本用不上。InnoDB 表损坏的正确处理是先备份,再在 my.cnf 里设 innodb_force_recovery = 1(必要时逐级加到 4),把数据导出来,然后重建表再导回。整个过程前提是有备份,所以 备份恢复 的演练不能省。
wp_options 膨胀:连接慢和超时的常见来源
有一类「时好时坏」的连接错误,其实是查询太慢导致超时。wp_options 表在每次请求时都会被完整加载 autoload 的部分,这张表一旦膨胀到几十 MB,每个页面都要多花几百毫秒,并发一高就报 MySQL server has gone away。
先看谁在占空间:
SELECT option_name, LENGTH(option_value) AS len
FROM wp_options
WHERE autoload = 'yes'
ORDER BY len DESC
LIMIT 20;
SELECT SUM(LENGTH(option_value)) / 1024 / 1024 AS autoload_mb
FROM wp_options
WHERE autoload = 'yes';
autoload 总量超过 1MB 就该处理,超过 5MB 会明显拖慢。常见来源是过期 transient、卸载插件留下的设置项、以及主题存进去的大量演示数据。清理时按类型分开处理:
-- 过期临时缓存,删了会自动重建
DELETE FROM wp_options WHERE option_name LIKE '_transient_%';
DELETE FROM wp_options WHERE option_name LIKE '_transient_timeout_%';
-- 查询后再决定,别直接跑
SELECT option_name FROM wp_options
WHERE option_name LIKE '%旧插件前缀%';
第一条可以放心执行,transient 本来就是缓存;第二条必须先看清楚再删,很多设置项名字看不出属于谁,删错会导致主题配置丢失。如果装了 Redis 之类的对象缓存,transient 会被接管,这时清表意义不大,反而要清缓存。wp_postmeta 里的孤儿数据(post_id 对应的文章已经不存在)也是同样的道理,能清出可观的体积,方法在 数据库优化 里。
可执行的定位顺序
- 确认报错原文属于哪一类,是连接失败、选库失败还是表损坏。
- 用最小 PHP 脚本直连,拿到 mysqli 的原始错误,别再猜。
- 核对
wp-config.php四个常量,注意空格、引号、库名前缀。 - 在命令行用同组账号连一次,区分「凭据问题」和「网络问题」。
- 切换
localhost与127.0.0.1,看是否 socket 路径问题。 - 查服务状态、端口监听、磁盘空间、错误日志最后 50 行。
- 连得上但页面报错,走 CHECK/REPAIR 或 InnoDB 恢复流程。
- 都排除了,再从最近的备份恢复——先恢复数据库,再考虑文件。
顺序不要跳。跳过第 2 步直接去改配置,很容易把一个原本只是 socket 路径的小问题,改成账号密码也错、库名也错的复合问题。同样的道理,如果整站打不开但不只报数据库错误,先按 打不开排查 的流程确认是 PHP 层还是数据库层,再回来处理这一篇的内容,分类更细的报错可以看 数据库报错。
数据库连接错误里真正危险的不是连不上,而是在没有备份的情况下连不上。修复之前先把 /var/lib/mysql 整个目录复制一份,这一步花不了几分钟,但能把「修坏了」变成「重来一次」。信科网络处理这类故障时也是先保数据再谈恢复,需要人上手可以打 13280692153。