PHP连不上MySQL时最常见的两个报错是SQLSTATE[HY000] [2002] No such file or directory和SQLSTATE[HY000] [2002] Connection refused。错误码都是2002,但一个走的是Unix域套接字,一个走的是TCP,排查路线完全不同。把host从localhost改成127.0.0.1,报错串会立刻从前者跳到后者,这个变化本身就是最直接的诊断线索。
两种报错串对应两条连接路径
2002是MySQL客户端库的CR_CONN_HOST_ERROR,只表示"没连上",不区分具体原因。真正能分诊的是后面的描述文本:
# 走Unix socket,socket文件找不到或没权限 SQLSTATE[HY000] [2002] No such file or directory SQLSTATE[HY000] [2002] Permission denied # 走TCP,对端没有进程在听这个端口 SQLSTATE[HY000] [2002] Connection refused SQLSTATE[HY000] [2002] php_network_getaddresses: getaddrinfo failed: Name does not resolve
最后一种是主机名压根没解析出来,属于写错了数据库地址,跟MySQL本身没关系。
先确认PHP实际走了哪条路
PDO_MYSQL对localhost这个主机名有特殊处理:host写成localhost时连接走Unix域套接字,host写成127.0.0.1才强制走TCP,这条规则写在PDO_MYSQL官方文档的连接说明里,mysqli同样如此。想知道当前PHP认的是哪个socket路径,直接问它:
php -i | grep -i socket
php --ri pdo_mysql
php -r 'var_dump(ini_get("pdo_mysql.default_socket"));'
输出里pdo_mysql.default_socket和mysqli.default_socket那两行就是答案。用mysqlnd编译的版本(Ubuntu、Debian的php-mysql包默认就是它)会读这两个ini项;用libmysqlclient编译的版本认的是编译时写死的位置,改ini对它无效,只能改用TCP或者在DSN里显式写unix_socket。
把服务端的真实socket路径找出来
服务端实际用哪个socket由my.cnf里的socket项决定,跟PHP那边的默认值经常对不上,这就是No such file or directory的直接来源。实测过的几个常见位置:
mysql -uroot -p -e "show variables like 'socket';" ss -xl | grep mysql # Debian、Ubuntu包安装 /var/run/mysqld/mysqld.sock # RHEL、CentOS系 /var/lib/mysql/mysql.sock # 源码编译或老环境 /tmp/mysql.sock # macOS上的MAMP /Applications/MAMP/tmp/mysql/mysql.sock
遇到过一台机器PHP那边指向/tmp/mysql.sock,服务端实则在/var/run/mysqld/mysqld.sock,改host能连上,但一直往目录权限上想,多花了小半天。还有个细节:systemd管理的MySQL在/var/run下建socket,而/var/run是tmpfs,重启后目录要重新创建,偶尔目录没建起来也会报一模一样的错。
让两边对上:改ini还是改DSN
三种做法,按侵入性从小到大排。
1、在DSN里直接写socket路径,不动全局配置:
$pdo = new PDO( 'mysql:unix_socket=/var/run/mysqld/mysqld.sock;dbname=shop;charset=utf8mb4', 'app_user', 'secret' );
2、改php.ini让整个环境统一。mysql扩展从PHP 7.0起就已经移除,只需要管下面这两个:
pdo_mysql.default_socket=/var/run/mysqld/mysqld.sock mysqli.default_socket=/var/run/mysqld/mysqld.sock
3、host改成127.0.0.1强制走TCP,顺手把端口也带上:
$pdo = new PDO('mysql:host=127.0.0.1;port=3306;dbname=shop;charset=utf8mb4', 'app_user', 'secret');
实际推荐第3种。socket路径在不同发行版、不同安装方式之间差异太大,容器里更是各写各的,而TCP这条路的行为在所有环境下都一致。代价是本机通信多绕一次协议栈,这点开销可以忽略。
换成127.0.0.1之后报Connection refused的六种情形
1、mysqld根本没起来。systemctl status mysql看状态,起不来就去翻/var/log/mysql/error.log。
2、skip-networking开着。SHOW VARIABLES LIKE 'skip_networking';值为ON时MySQL完全不开TCP监听,只剩socket一条路。配置项在/etc/mysql/mysql.conf.d/mysqld.cnf里,注释掉重启即可。
3、bind-address绑得太死。默认127.0.0.1只允许本机连,容器或者另一台机器访问时用ss -tlnp看监听地址到底是0.0.0.0、内网IP还是只有回环:
ss -tlnp | grep 3306 nc -zv -w 3 10.0.0.7 3306
4、端口不是3306。MAMP默认用8889,docker-compose里常把3306映射成3307或者33060。这时候ss -tlnp看到什么端口,DSN里就写什么端口,别相信默认值。
5、容器里写127.0.0.1指的是容器自己,既不是宿主机也不是db服务。compose网络里host要写服务名db,要连宿主机则用host.docker.internal或者宿主机内网IP。
6、中间有防火墙。跨机的话除了iptables还要看云厂商安全组,两头都要放。
2002之外:2003、1045、2054怎么区分
2003 Can't connect to MySQL server on '10.0.0.7' (111)同样是TCP层连不上,多见于远程场景,处理方式和上面的第6条一致。
1045 Access denied for user 'app'@'127.0.0.1'是连上了但账号不对。这里有个容易踩的坑:'app'@'localhost'和'app'@'127.0.0.1'在MySQL里是两个不同的账号,走socket算localhost,走TCP算127.0.0.1。之前有次把host从localhost换成127.0.0.1之后报1045,原因就是只授权了localhost那个账号。
2054 The server requested authentication method unknown to the client是认证插件不兼容。MySQL 8.0.36、8.0.40、8.4.0、8.4.4,的默认认证插件都是caching_sha2_password,PHP 7.4以下的mysqlnd不认它。PHP 7.4、8.0、8.1、8.2、8.3、8.4、8.5,这几个版本里只有7.4之前的会撞上;真要改认证方式的话注意MySQL 8.4已经把mysql_native_password插件默认关掉了,与其折腾服务端,不如直接升级PHP。
php.ini改了却没生效
CLI和PHP-FPM读的php.ini经常不是同一个文件。改完必须确认生效位置:
php --ini # Web环境看phpinfo()里的 Loaded Configuration File # 以及 Scan this dir for additional .ini files
改完FPM要重启进程才读得到新配置,reload不够就systemctl restart php8.4-fpm。见过一次改了ini却始终报旧路径,最后发现是conf.d目录下还有个更早加载的自定义ini把它覆盖了。
两个容易混的参数
max_connect_errors:某个客户端连续失败超过这个数会被拉黑,直到执行FLUSH HOSTS才恢复,报错是Host '10.0.0.7' is blocked because of many connection errors。测试环境反复试错时容易撞上,现象很像连不上,其实是自己把自己封了。
max_connections:默认151,打满时报的是1040 Too many connections,不是2002。分不清就把完整报错串贴出来再判断。
数据截至2026年10月。