 超时排查与修复)
前几天帮朋友看一台内网测试机他在自己电脑上敲下mysql -h 192.168.172.130 -uroot -p回车之后光标卡了十几秒最后蹦出来一行ERROR 2002 (HY000): Cant connect to server on 192.168.172.130 (115)。他第一反应是密码错了改了三次密码第二反应是 MySQL 没启动systemctl status mysqld一看活得好好的。这就是这个报错最坑的地方——它长得像认证问题实际上是连接根本没有到达 MySQL 服务进程。MySQL 远程连接这个场景几乎每个后端、运维、做课程作业的人都会撞上一次尤其是一台虚拟机里装好 MySQL、宿主机或者另一台机器去连的时候。这篇内容我会把 ERROR 2002 这条报错从错误码含义讲到分层排查再到六类典型成因的实操修复、完整的打通流程以及我自己踩过的坑全部铺开讲清楚。不管你是刚装完 MySQL 的新手还是接手了一台不知道被谁改过配置的服务器都能照着往下走。1. 先搞懂这条报错到底在说什么1.1 报错信息逐段拆解ERROR 2002 (HY000): Cant connect to server on 192.168.172.130 (115)这句话的信息密度其实很高只是大多数人被最后那个 (115) 带偏了注意力。ERROR 2002是 MySQL 客户端层面的错误码不是服务端返回的。这一点必须先钉死MySQL 服务端返回的错误都是四位数带 SQLSTATE 的格式比如ERROR 1045 (28000): Access denied那个28000是 SQLSTATE代表认证失败类。而 2002 属于客户端错误码体系CR_CONN_HOST_ERROR 一脉意味着客户端在自己的连接阶段就失败了压根没走到服务端说行不行这一步。换句话说你的用户名密码对不对此刻根本不重要因为双方还没握上手。(HY000)是 SQLSTATEHY000是个通用/未分类的兜底状态码客户端本地错误基本都是它所以这个字段提供不了额外信息不用纠结。on 192.168.172.130是客户端尝试连接的目标地址这个值来自你命令行里的-h参数或者配置文件里的 host 字段。(115)是最关键的部分它是操作系统层面的 errno由底层 socket 系统调用失败后由客户端打印出来。很多人看到 115 会以为是 MySQL 的错误码其实不是它是 glibc 的 errno 编号。在 Linux 环境下errno 115 对应的是EINPROGRESSOperation now in progress操作正在进行中。这个名字听起来很正面好像连接在推进——但注意MySQL 客户端是用非阻塞 socket 发起 connect 的它会用自己的connect_timeout默认 10 秒左右去等待可写事件。如果等满了超时时间socket 仍然处于进行中的状态客户端就会把这个最后残留的 errno 直接打印出来。所以(115) 在实战里的真实语义就是连接超时对端没有任何回应。1.2 (115) 和 (111)、(113) 的区别决定了排查方向把这个错误码吃透价值在于它能直接帮你缩小排查范围。Linux 环境下这几个 errno 在 MySQL 连接场景里非常常见含义完全不同错误码含义典型成因排查方向(115)连接超时无任何回应防火墙 DROP 丢包、路由不通、安全组拦截网络层与防火墙(111)Connection refused端口没有进程监听或明确被拒绝服务是否启动、bind-address(113)No route to host路由不可达、目标主机不在同网段网卡、路由表、VLAN(110)Connection timed out与 115 高度类似多见于非 Linux 平台同 115关键差别在 (115) 和 (111) 之间。防火墙规则有两种写法DROP是把包静默丢掉发起方永远等不到任何回包最终就是超时报 (115)REJECT是主动回一个 ICMP 端口不可达或者 TCP RST发起方立刻收到拒绝报 (111)。这个区别实战意义极大看到 (115)八成是路上被静默拦截了看到 (111)八成是服务端根本没在监听那个地址。很多教程上来就让人改bind-address但如果你的报错是 (115)改 bind-address 大概率白忙一场因为包连主机都没到服务怎么绑定根本不参与决策。反过来如果是 (111)那基本可以直接排除网络设备直奔 MySQL 配置和用户授权。1.3 为什么本地能连、远程就连不上这是新手最困惑的点在服务器上敲mysql -uroot -p一切正常换成另一台机器就报 2002。原因在于 MySQL 的连接路径不止一条。MySQL 客户端连接时如果 host 是localhostLinux 下会优先走Unix Socket 文件通常是/var/lib/mysql/mysql.sock或/tmp/mysql.sock这条路完全不经过 TCP/IP 协议栈不需要监听端口自然也不受bind-address和防火墙影响。而一旦你用-h 192.168.172.130或者-h 127.0.0.1连接方式就变成了TCP需要服务端在对应网卡上真正监听需要网络可达需要防火墙放行。所以本地能连这个事实证明的只是 MySQL 进程活着、socket 文件在、账号至少对本地可用。它证明不了任何关于 TCP 监听和网络通路的结论。我见过太多人在这一步绕圈反复确认服务在跑、反复重启 MySQL、甚至重装数据库就是没意识到自己验证的是另一条完全不同的链路。提示想快速判断本地走的是哪条路连上之后执行status输出里有一行Connection: Localhost via UNIX socket或者Connection: 192.168.172.130 via TCP/IP一目了然。2. 排查思路从网络层到应用层的五层过滤2.1 分层定位法别一上来就改配置远程连不上这个问题可能出问题的地方从下往上有五层我在实际排查时习惯按这个顺序走绝不跳步第一层物理与链路两台机器是否在能互相到达的网络里。虚拟机网络模式NAT、桥接、仅主机在这里起决定作用NAT 模式下宿主机访问虚拟机的 IP 往往会有各种意外。第二层IP 可达性ping能不能通。注意 ping 通不代表 TCP 通很多防火墙只拦 TCP 不拦 ICMP。第三层端口可达性目标主机的 3306 端口能不能建立 TCP 连接。这一层能过滤掉 90% 的问题。第四层服务监听MySQL 是否在能被外部访问的地址上监听 3306。第五层账户授权mysql.user表里是否存在允许该来源主机连接的账号。这五层的顺序不能乱。道理很简单如果包在第三层就被防火墙丢了你去第五层调 GRANT 语句是毫无意义的动作——改完了照样连不上还会让你误以为授权没用进而怀疑 MySQL 本身有 bug。我给自己定的规矩是每一层必须有明确的、可观测的证据证明它通过才进入下一层。没有证据的应该没问题在排查里等同于未知。2.2 必备的排查工具箱下面这些命令是排查这条报错的全部家当建议先确认目标机器上装没装没装的话提前装好出事的时候现装很耽误事。ping —— 验证第二层ping -c 4 192.168.172.130不通的话先看两端 IP 是否在同一网段、掩码对不对、路由表里有没有对应条目。虚拟机场景要额外确认网络模式桥接模式下虚拟机会拿到和宿主机同网段的 IP通常最省事NAT 模式下虚拟机的 192.168.x.x 是虚拟机内部网段宿主机默认是能访问的但其他物理机就不行。telnet 或 nc —— 验证第三层# 老派但通用几乎所有 Linux 都自带 telnet 192.168.172.130 3306 # 更推荐能直接给结论 nc -vz -w 3 192.168.172.130 3306nc -vz的-v输出详细信息-z表示只扫描不发送数据-w 3是 3 秒超时。返回succeeded说明端口通返回Connection refused说明被明确拒绝多半是没监听返回timed out就是典型的静默丢包——和你的 (115) 同一个病因。ss —— 验证第四层ss -tlnp | grep 3306输出会明确告诉你监听地址。三种可能127.0.0.1:3306—— 只监听回环外部必然连不上0.0.0.0:3306或:::3306—— 监听所有地址正常什么都不输出 —— 服务没启动或者被skip-networking关掉了 TCPnetstat -tlnp | grep 3306效果一样但 netstat 在新发行版里逐渐被移除建议养成用 ss 的习惯。mysql 客户端自带参数 —— 验证第五层mysql -h192.168.172.130 -P3306 -uapp -p --connect-timeout5--connect-timeout是客户端参数单位秒。默认值在有些版本里长达 10 秒甚至更久排查时手动压到 3 到 5 秒能显著减少等待。注意这个参数只影响连接阶段连上之后执行长查询超时用的是另一套参数。tcpdump —— 抓包看真相# 在客户端执行观察发出的 SYN 有没有回音 tcpdump -i any -nn tcp port 3306这是终极手段。如果只看到本机发出的SYN一直没有SYN-ACK那就是标准的路被堵了如果看到SYN之后立刻回来一个RST, ACK那就是对端明确拒绝。同一时刻在服务端也抓一份能立刻判断包到底有没有到达目标主机——这一招在云服务器安全组问题上特别好用能直接证明包卡在了云平台层面还是卡在了主机上。2.3 一张速查表锁定问题层实际排查时把现象和这张表对一遍基本能锁定方向现象高概率原因优先验证命令ping 不通网络模式、路由、IP 配置ip addr、ip routeping 通但 nc 超时防火墙 DROP、云安全组tcpdump双向抓包nc 报 Connection refusedMySQL 未启动或未监听 TCPss -tlnp | grep 3306端口通但报 1045账号密码或授权 host 不匹配SELECT user,host FROM mysql.user;端口通但报 1130授权主机段不允许同上时通时不通连接数打满、网络抖动SHOW STATUS LIKE Threads_connected;这张表的价值在于它把报错文案翻译成了要执行的命令避免你在没有方向的情况下漫无目的地翻配置文件。3. 逐个击破六类典型成因的实操修复3.1 bind-address 只监听回环地址这是最经典的一条。MySQL 的bind-address参数决定它在哪块网卡上监听 TCP。很多发行版尤其是 Debian/Ubuntu 的 apt 包默认在配置文件里写了bind-address 127.0.0.1意思是只听本机回环。这种配置下从任何其他机器发来的包都会被内核直接拒绝客户端看到的是 (111) 而不是 (115)——但如果中间还叠加了防火墙 DROP就可能表现为 (115)。修复方式找到配置文件把 bind-address 改成0.0.0.0或者直接注释掉。配置文件位置因发行版而异常见位置如下# CentOS / RHEL / Rocky /etc/my.cnf # 也可能在 /etc/my.cnf.d/mysql-server.cnf # Ubuntu / Debian /etc/mysql/mysql.conf.d/mysqld.cnf /etc/mysql/my.cnf改之前先确认哪个文件真正生效别改了半天发现改的是个不会被加载的文件mysqld --verbose --help | grep -A 1 Default options或者连上数据库执行SHOW VARIABLES LIKE bind_address; SHOW VARIABLES LIKE datadir;我一般习惯用SHOW VARIABLES来反查因为它的输出是实际生效值不受配置文件层级干扰。改完配置必须重启服务systemctl restart mysqld # Ubuntu 上是 mysql systemctl restart mysql重启后再用ss -tlnp | grep 3306确认监听地址变了。如果还是127.0.0.1说明你改的文件没被加载或者同一个参数在别的文件里被后面的配置覆盖了。MySQL 的配置加载是有顺序的后面的覆盖前面的!includedir引入的目录会按文件名字母序加载。注意bind-address在 MySQL 8.0 里还涉及 IPv6。如果你的服务器同时有 IPv4 和 IPv6写成0.0.0.0只监听 IPv4写成::通常能同时接受两者。有些环境里客户端解析到 IPv6 地址去连就会出现一些很诡异的超时排查时可以用-h强制指定 IPv4 地址来验证。3.2 授权表里没有允许远程来源的账号端口通了但连上去报ERROR 1045 (28000): Access denied for user root192.168.172.1——注意这里的 (28000) 就不是连接层问题了说明包已经成功送到 MySQL是认证环节被拒。这种情况属于另一类问题但它经常和 2002 混在一起被误判所以放在这里一起说。MySQL 的权限模型是用户 来源主机二元组。rootlocalhost和root%是两个完全不同的账号一点关系都没有。默认安装完只有rootlocalhost所以远程连就是没有匹配的账号直接拒绝。MySQL 8.0 的正确做法是先建用户再授权不要用GRANT隐式创建用户8.0 之后这个行为已经废弃-- 建一个只允许 192.168.172 网段连接的账号 CREATE USER app192.168.172.% IDENTIFIED BY YourStrongPass!2024; -- 只给业务库权限不要一上来就 ALL ON *.* GRANT SELECT, INSERT, UPDATE, DELETE ON appdb.* TO app192.168.172.%; FLUSH PRIVILEGES;几个必须注意的点一是%不匹配localhost。这是 MySQL 里著名的坑。%在主机匹配规则里代表除 localhost 之外的所有主机因为localhost被特殊对待走 socket。所以如果你建了app%却没建applocalhost本机反而连不上。稳妥做法是建两个账号或者干脆用app%加上applocalhost。二是网段写法要精确。192.168.172.%匹配 192.168.172.0/24 这个网段比%安全得多。生产环境即使在内网也不要用%万一哪天机器暴露到公网就是灾难。三是不要长期用 root 远程。我见过不少教程让新手直接UPDATE mysql.user SET host% WHERE userroot改完之后确实能连了但这是把一个超级权限账号敞开在网络里。正确做法是给业务单独建账号、单独授权root 保持只允许本地。四是改完要FLUSH PRIVILEGES。用GRANT/CREATE USER语句操作时MySQL 会自动刷新内存中的权限表但如果你是直接UPDATE mysql.user表就必须手动执行FLUSH PRIVILEGES否则改的只是硬盘上的表内存里还是旧权限。验证账号是否创建成功SELECT user, host, plugin FROM mysql.user WHERE user app;MySQL 8.0 默认认证插件是caching_sha2_password。老版本的客户端工具比如一些用了很久的 Navicat、老版 PHP 的 mysql 扩展不支持这个插件会在认证阶段报错。如果你确实需要用老客户端可以指定IDENTIFIED WITH mysql_native_password BY xxx但更推荐的做法是升级客户端别为了兼容把服务端的认证强度降下来。3.3 防火墙与云安全组拦截如果ss -tlnp显示监听是0.0.0.0:3306账号也建好了但 nc 还是超时那就到防火墙环节了。这一层是 (115) 最常见的成因因为它默认就是静默丢包。主机防火墙按发行版分# firewalldCentOS 7 / RHEL 系 firewall-cmd --permanent --add-port3306/tcp firewall-cmd --reload # 确认结果 firewall-cmd --list-ports # ufwUbuntu / Debian ufw allow 3306/tcp ufw status # 老式 iptables iptables -I INPUT -p tcp --dport 3306 -j ACCEPT # 记得持久化否则重启就没了 service iptables save # CentOS 6 时代 # 或者用 iptables-persistent云平台安全组是另一个独立层。如果你用的是云主机安全组规则是独立于操作系统防火墙的两边都要放行。安全组的特点是它工作在云平台的虚拟网络层你在主机上用 tcpdump 抓不到任何东西——因为包根本没到主机。这也是我前面强调两侧同时抓包的原因客户端抓到 SYN、服务端抓不到就是云平台层面的问题这时候去翻操作系统防火墙纯属浪费时间。排查安全组的时候我一般直接上 tcpdump# 客户端侧 tcpdump -i any -nn host 192.168.172.130 and tcp port 3306 # 服务端侧 tcpdump -i any -nn tcp port 3306两边对照包到没到一秒见分晓。提示有些环境的防火墙规则是按 zone 走的--add-port要确认加到了正确的 zone一般是 public。用firewall-cmd --get-active-zones先看清楚哪个网卡属于哪个 zone加错 zone 是白加。3.4 skip-networking 与端口占用skip-networking这个参数一旦开启MySQL 会完全关闭 TCP/IP 监听只保留 socket 连接。这种配置在某些安全加固教程里会出现也有的是被人误加进去的。检查方式SHOW VARIABLES LIKE skip_networking;返回ON就说明 TCP 被关了。把它改成OFF或者在配置文件里注释掉skip-networking这一行重启服务即可。相关联的还有一个port 0也是关闭 TCP 监听的另一种写法。另一种情况是端口被别的进程占了。虽然大概率 MySQL 会因为端口冲突直接启动失败而不是静默但如果配置了--skip-grant-tables或者一些特殊启动参数可能出现诡异状态。检查ss -tlnp | grep 3306 # 输出里最后一段就是进程名和 PID如果占着 3306 的不是 mysqld那就要处理冲突。还有一个小细节MySQL 8.0 除了 3306还会开一个 33060 端口给 X Protocol 用这个端口和主连接无关别混淆。另外有些云服务商或者容器环境里mysqld被配置成监听非标准端口比如 3307。这种情况下你的-P参数必须跟着改mysql -h192.168.172.130 -P3307 -uapp -p我遇到过一位朋友配置文件里 port 被某次优化改成了 3307他一直用 3306 连报的也是 2002折腾了大半天才发现。所以查SHOW VARIABLES LIKE port;应该是排查清单里的固定动作。3.5 SELinux 与代理协议干扰SELinux在 CentOS/RHEL 系默认是 enforcing 状态。它和防火墙是两套独立机制防火墙管的是包能不能进来SELinux 管的是进来之后 mysqld 这个进程能不能绑定那个端口。如果 MySQL 想监听非标准端口比如 3307SELinux 会直接阻止日志里会有 avc denied 的记录。检查和处理getenforce # 返回 Enforcing / Permissive / Disabled # 查看有哪些和 mysql 相关的布尔值 getsebool -a | grep mysql # 允许 mysqld 连接任意端口谨慎使用 setsebool -P mysql_connect_any 1 # 或者更精确地给端口打标签 semanage port -a -t mysqld_port_t -p tcp 3307我个人倾向于精确打标签而不是直接开mysql_connect_any尤其是在合规要求比较严的环境里。如果只是临时验证问题是不是 SELinux 引起的可以setenforce 0临时切到宽容模式测一次确认是它干的再去做正式配置测完记得切回去别把setenforce 0当成最终方案。代理协议Proxy Protocol是另一类比较隐蔽的干扰。如果你前面挂了负载均衡或者某些代理代理可能开启了 Proxy Protocol 模式它在原始 TCP 数据前面插了一段额外的头部信息。MySQL 本身不认识这个头部会直接拒绝连接。这个坑比较小众但确实存在特征是直连 IP 正常走代理就报连接层错误。确认方法是在代理侧关掉 Proxy Protocol 试一次。顺带提醒一个概念上的混淆MySQL 自己有一套proxy_user机制PROXY权限那是做用户映射用的和网络层的代理协议完全是两回事不要因为名字像就往一起想。3.6 连接数打满与超时参数调优服务端有个max_connections参数默认常见是 151。当并发连接达到上限时新连接会被拒绝报的是ERROR 1040 (HY000): Too many connections这是另一条错误。但如果配合连接池的连接慢、以及大量的半开连接也可能表现为连接阶段长时间等待最终超时。排查思路SHOW VARIABLES LIKE max_connections; SHOW VARIABLES LIKE connect_timeout; SHOW STATUS LIKE Threads_connected; SHOW STATUS LIKE Max_used_connections; SHOW STATUS LIKE Aborted_connects;Max_used_connections如果接近max_connections说明连接池配置有问题Aborted_connects持续增长则说明有大量连不上的尝试可能来自配置错误的客户端或者扫描。connect_timeout是服务端参数默认 10 秒指的是等待握手完成的时间和客户端的--connect-timeout是两个不同的东西。排查超时的时候把客户端的--connect-timeout压小能让你的验证循环快很多。短期应急可以调大max_connectionsSET GLOBAL max_connections 500;但请注意这只是临时生效重启就回去了。永久生效要写进配置文件。而且调大连接数本质上是掩盖问题——真正的解法是修复连接泄漏让连接池的上限和数据库的上限对齐。4. 完整实操从零打通一台远程 MySQL4.1 服务端配置三个参数一次搞定假设你在一台 IP 为 192.168.172.130 的 Linux 机器上刚装好 MySQL 8.0现在要让网段内另一台机器连上。按顺序来第一步确认服务在跑且监听正确。systemctl status mysqld ss -tlnp | grep 3306如果监听显示127.0.0.1:3306进入第二步。第二步改配置文件。先定位生效的配置文件mysqld --verbose --help 2/dev/null | grep -m1 Default options比如输出指向/etc/my.cnf就在[mysqld]段落下加[mysqld] bind-address 0.0.0.0 port 3306 max_connections 300 character-set-server utf8mb4 collation-server utf8mb4_0900_ai_ciutf8mb4这一项不是必须的但我建议顺手加上避免后面出现中文乱码再来回折腾。utf8mb4_0900_ai_ci是 8.0 的默认排序规则显式写出来更清晰。第三步重启并验证。systemctl restart mysqld ss -tlnp | grep 3306现在应该看到0.0.0.0:3306。4.2 账户授权最小权限原则连上本地数据库建远程账号CREATE USER app192.168.172.% IDENTIFIED BY Str0ng!Passw0rd; CREATE DATABASE IF NOT EXISTS appdb DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_0900_ai_ci; GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, INDEX, ALTER ON appdb.* TO app192.168.172.%; FLUSH PRIVILEGES; SELECT user, host, plugin FROM mysql.user WHERE user app;注意我这里给的是具体权限列表而不是ALL PRIVILEGES。ALL包含DROP、GRANT OPTION这些危险权限业务账号根本用不上。同理appdb.*限定到具体库而不是*.*。这套最小权限写法麻烦一点但一旦哪天账号泄露损失范围可控。character_set和collate我在建库时显式指定了这样即使服务端配置被谁改过这个库的字符集也不会漂移。4.3 客户端验证按层逐步确认在另一台机器上按顺序执行# 1. 网络可达 ping -c 3 192.168.172.130 # 2. 端口可达 nc -vz -w 3 192.168.172.130 3306 # 3. 认证与授权 mysql -h192.168.172.130 -P3306 -uapp -p --connect-timeout5 appdb -e SELECT NOW(), USER(), CURRENT_USER();第三条命令的输出很有价值USER()返回的是客户端声明的身份CURRENT_USER()返回的是实际匹配到的账号。如果两者不一致说明匹配到了另一个账号比如你以为连的是app192.168.172.%实际匹配了app%这在排查权限问题时是决定性的证据。我强烈建议把这三条命令写成一个脚本以后每换一个环境就跑一遍比凭记忆一条条敲靠谱。4.4 事后加固三件必须做的事打通之后别急着收工这三件事不做后面一定会出问题。第一件别把 root 开远程。如果你之前为了图省事改过mysql.user里 root 的 host现在改回来-- 先确认有没有被改过 SELECT user, host FROM mysql.user WHERE user root; -- 如果有 %删掉这条记录 DROP USER root%;第二件检查是否有匿名账号。有些老版本的初始化脚本会建空用户名的账号这是个安全隐患SELECT user, host FROM mysql.user WHERE user ; DROP USER localhost;第三件把改动记录下来。我吃过这个亏半年前给别人配的机器半年后出问题没人记得当时改过哪些参数。现在的做法是每次动完配置都在机器上留一个/root/mysql-notes.md记录改了什么文件、什么参数、为什么改。成本极低收益极高。注意如果确实需要从公网访问正确做法是加一层跳板或者用带认证的隧道方案而不是把 3306 直接暴露出去。3306 是全网扫描的重点目标暴露在公网上的 MySQL 实例快的话几小时内就会收到暴力破解尝试。5. 踩坑实录与常见问题速查5.1 那些让我熬夜的几个坑坑一虚拟机网络模式的坑。有次在 VMware 里用 NAT 模式装 CentOS宿主机能 ping 通虚拟机的 192.168.x.x但连 3306 一直超时。查了半天防火墙、bind-address 都没问题最后发现是 VMware 的 NAT 网络设置里DHCP 分配的网段和虚拟机静态 IP 冲突了。改成桥接模式一切正常。教训是虚拟机环境下网络模式应该作为第一优先级确认而不是最后才想起来。坑二改了配置文件但没重启。MySQL 里确实有一些参数支持在线修改SET GLOBAL但bind-address、port、skip-networking这些网络层参数是必须重启的。我见过有人改完配置直接测试测不通就以为改错了方向来回折腾一小时。改完先重启再验证。坑三%和localhost的匹配混淆。这个坑我踩过不止一次。建了app%远程连不上——因为远程那台机器解析出来的主机名恰好是localhost在做 hosts 映射的测试环境里会发生。或者反过来本机连不上因为只建了app%。稳妥做法是建账号时把applocalhost也补一个。坑四客户端配置文件的隐形干扰。MySQL 客户端会读取/etc/my.cnf、~/.my.cnf等文件如果里面写了[client]段落的host或者port会覆盖你命令行的部分参数。有次我明明写了-h192.168.172.130实际连的却是另一台机器查了半天才发现是~/.my.cnf里的 host 在作祟。排查时可以用mysql --print-defaults看看客户端实际加载了哪些默认参数。坑五--connect-timeout的理解偏差。这个参数只作用于建连阶段。有次我把它设成 3 秒结果连接建立之后的慢查询还是跑很久我一度以为参数没生效。后来才想明白建连和执行查询是两个阶段后者要调的是max_execution_time或者客户端的读超时设置。5.2 常见问题速查报错/现象直接原因一行命令定位ERROR 2002 (115)连接超时包被静默丢弃nc -vz -w 3 host 3306ERROR 2002 (111)端口无监听或被拒绝ss -tlnp | grep 3306ERROR 1045 (28000)账号密码错或 host 不匹配SELECT user,host FROM mysql.user;ERROR 1130 (HY000)该来源主机未被授权同上检查 host 字段ERROR 1040 (HY000)连接数打满SHOW STATUS LIKE Threads_connected;ERROR 1290 (HY000)服务端处于 --skip-grant-tables 状态SHOW VARIABLES LIKE skip_grant_tables;连上后中文乱码字符集不一致SHOW VARIABLES LIKE character_set%;时通时断连接池配置、网络抖动查Aborted_connects、抓包表里那条 1290 值得单独说一句如果服务端带着--skip-grant-tables启动所有权限检查会被跳过此时执行任何需要写权限的操作都会报 1290。这个状态下的 MySQL 是完全不设防的任何人不需要密码就能连上一旦排查完必须立刻恢复正常的启动参数并重启。5.3 日志才是最诚实的证人所有命令都试过还是没头绪的时候回到日志。MySQL 的错误日志里往往直接写着原因只是大多数人不去看。# 先找到日志位置 mysql -e SHOW VARIABLES LIKE log_error; # 然后直接看 tail -n 100 /var/log/mysqld.log # 或者 journalctl -u mysqld -n 100 --no-pagerjunctionctl这一招在 systemd 系统上特别好用因为很多发行版把 MySQL 的日志直接导向了 journal/var/log/mysqld.log里反而什么都没有。日志里要重点关注几类信息启动时有没有报Cant start server: Bind on TCP/IP port这说明端口冲突有没有 Access denied for user 的连续记录这说明有人在暴力尝试有没有 Too many connections说明连接数问题。最后分享一个我自己的习惯每次远程连接出问题我会先在客户端跑一次带超时的 nc同时开一个 tcpdump 窗口。三秒之内是路不通、是端口不响应、还是认证被拒基本就定性了。比翻配置文件快得多也比凭经验猜靠谱得多。这套动作做熟之后从看到报错到定位到根因通常不会超过五分钟。