MySQL 解除 Host is blocked

摘要

报错代表什么

客户端连不上时,服务器返回错误号 1129,原文是:

1
2
Host 'host_name' is blocked because of many connection errors.
Unblock with 'mysqladmin flush-hosts'

有的客户端会合成一行,中间用分号。解除封锁的语句是:

1
TRUNCATE TABLE performance_schema.host_cache;

MySQL 在内存里维护 host_cache,做两件事:缓存客户端 IP 到主机名的 DNS 结果;记录连接过程中的错误。某个 IP 的协议握手连续失败,次数达到全局变量 max_connect_errors(默认 100)之后,服务器拒绝这个 IP 后续新建立的 TCP 连接。

这张缓存只用于非本机的 TCP 连接。走 127.0.0.1、::1、Unix socket、命名管道或共享内存的连接不进缓存,也不会被这条规则封锁。远程 IP 被封之后,仍可以在数据库本机用 socket 连上去执行上面的 TRUNCATE。

网络抖动,或连接池在握手阶段大量失败重连时,共用一个出口 IP 的整组应用会一起被拒。密码错误记在 COUNT_AUTHENTICATION_ERRORS,不参与和 max_connect_errors 的比较。

达到阈值之前,这个 IP 一旦有一次连接成功,连续失败计数会清成 0。已经封上之后,后来的连接会被拒绝,计数不会自己回到 0。并发握手时,计数可以一次超过阈值:连接开始时还没到阈值的那几条仍可能成功,开始时已经到阈值的新连接才会被拒。

先查出是哪些主机

清空之前先查。下面这条语句只读缓存,不会解封。

1
2
3
4
5
6
7
8
-- SUM_CONNECT_ERRORS 大于 0:有过计入阈值的握手错误
-- COUNT_HOST_BLOCKED_ERRORS 大于 0:已经有连接因为越过阈值被拒绝
SELECT IP, HOST, HOST_VALIDATED, SUM_CONNECT_ERRORS,
COUNT_HANDSHAKE_ERRORS, COUNT_HOST_BLOCKED_ERRORS,
COUNT_AUTHENTICATION_ERRORS, FIRST_ERROR_SEEN, LAST_ERROR_SEEN
FROM performance_schema.host_cache
WHERE SUM_CONNECT_ERRORS > 0
OR COUNT_HOST_BLOCKED_ERRORS > 0;

查不到行,或提示表不存在时,先看 Performance Schema 是否打开,以及当前账号有没有这张表的 SELECT 权限:

1
2
SHOW VARIABLES LIKE 'performance_schema';
SHOW GRANTS;

performance_schema 为 OFF 时,performance_schema.host_cache 不存在,后面的 TRUNCATE 也会失败。8.0 和 8.4 默认是 ON。

列 作用
IP 客户端 IP
HOST 该 IP 解析出的主机名,解析失败时为 NULL
SUM_CONNECT_ERRORS 计入封锁阈值的握手错误次数,只统计 HOST_VALIDATED = YES 的主机
COUNT_HANDSHAKE_ERRORS 协议层握手错误次数
COUNT_HOST_BLOCKED_ERRORS 因已经越过阈值而被拒绝的连接次数
COUNT_AUTHENTICATION_ERRORS 认证失败次数,不参与和 max_connect_errors 的比较

列很多,单台主机用 \G 竖排看。下面是 max_connect_errors = 1000 时一台已经被封锁的主机:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
mysql> SELECT * FROM performance_schema.host_cache WHERE SUM_CONNECT_ERRORS > 0\G
*************************** 1. row ***************************
IP: 10.10.0.220
HOST: ip-10-10-0-220.cn-northwest-1.compute.internal
HOST_VALIDATED: YES
SUM_CONNECT_ERRORS: 1000
COUNT_HOST_BLOCKED_ERRORS: 242
COUNT_NAMEINFO_TRANSIENT_ERRORS: 0
COUNT_NAMEINFO_PERMANENT_ERRORS: 0
COUNT_FORMAT_ERRORS: 0
COUNT_ADDRINFO_TRANSIENT_ERRORS: 0
COUNT_ADDRINFO_PERMANENT_ERRORS: 0
COUNT_FCRDNS_ERRORS: 0
COUNT_HOST_ACL_ERRORS: 0
COUNT_NO_AUTH_PLUGIN_ERRORS: 0
COUNT_AUTH_PLUGIN_ERRORS: 0
COUNT_HANDSHAKE_ERRORS: 1000
COUNT_PROXY_USER_ERRORS: 0
COUNT_PROXY_USER_ACL_ERRORS: 0
COUNT_AUTHENTICATION_ERRORS: 0
COUNT_SSL_ERRORS: 0
COUNT_MAX_USER_CONNECTIONS_ERRORS: 0
COUNT_MAX_USER_CONNECTIONS_PER_HOUR_ERRORS: 0
COUNT_DEFAULT_DATABASE_ERRORS: 0
COUNT_INIT_CONNECT_ERRORS: 0
COUNT_LOCAL_ERRORS: 0
COUNT_UNKNOWN_ERRORS: 0
FIRST_SEEN: 2026-11-11 09:57:33
LAST_SEEN: 2026-11-12 06:38:33
FIRST_ERROR_SEEN: 2026-11-11 09:57:33
LAST_ERROR_SEEN: 2026-11-12 06:38:33
  • HOST_VALIDATED 为 YES,主机名已经解析出来。DNS 相关计数都是 0,这次封锁来自握手失败。

  • SUM_CONNECT_ERRORS 和 COUNT_HANDSHAKE_ERRORS 都是 1000,与当时的 max_connect_errors 相同。这 1000 次都是协议握手中断。

  • COUNT_HOST_BLOCKED_ERRORS 为 242。计数到达阈值之后,又有 242 次连接被拒绝。拒绝次数加在这一列,SUM_CONNECT_ERRORS 停在 1000。

  • COUNT_AUTHENTICATION_ERRORS 以及账号、SSL、连接数配额相关的列都是 0。

  • FIRST_SEEN 与 FIRST_ERROR_SEEN 相同,这条缓存从第一次见到该 IP 起就是错误。LAST_SEEN 与 LAST_ERROR_SEEN 也相同,最近一次活动仍是错误。从 2026-11-11 09:57:33 到 2026-11-12 06:38:33,这台主机一直在失败或被拒绝。时间按当前会话时区显示,见 MySql--时区与地区设置。

  • 主机名是这个内网 IP 的 DNS 记录。从 10.10.0.220 这台机器发起的连接都记在这一行上。

要让这台主机恢复连接,执行下一节的 TRUNCATE。只把 max_connect_errors 再调大,这 1000 次握手错误和后面的 242 次拒绝还在,封锁不会消失。

缓存满时,最久未使用的条目会被丢掉。丢掉的如果是一台已被封锁的主机,这台主机会因此恢复连接。条目还在、计数仍超过阈值时,要靠下一节的清空。

清空主机缓存

执行账号需要对 performance_schema.host_cache 有 DROP 权限。只有 RELOAD、没有这张表 DROP 的账号会报 DROP command denied。

1
2
-- 清空整张主机缓存。被封锁的 IP 可以立刻重试
TRUNCATE TABLE performance_schema.host_cache;

正常结果是 Query OK, 0 rows affected。这张 Performance Schema 表清掉行之后,返回值仍是 0。再用上一节的 SELECT 看:对应 IP 不再出现,或 SUM_CONNECT_ERRORS 为 0,封锁记录已经没了。客户端重新连上,才算服务恢复。查询结果变空,和客户端已经连上,是两件事。

这条语句清的是整张缓存。所有被封锁的主机都会恢复重试,服务器下次连接这些 IP 时要重新做 DNS 解析。

TRUNCATE 报 DROP command denied 时,用 SHOW GRANTS 看当前账号有没有这张表的 DROP,换有权限的账号再执行一次。

其他清空方式

FLUSH HOSTS 从 MySQL 8.0.23 起弃用,MySQL 8.4 已删除。8.4 上执行会报语法错误,用上一节的 TRUNCATE。

8.0.22 及更早版本可以用下面两条,效果和 TRUNCATE 相同。FLUSH HOSTS 需要 RELOAD 权限。

1
FLUSH HOSTS;
1
2
# 在仍能登录的机器上执行。-h 填 MySQL 服务器地址,命令打到这台服务器上
mysqladmin -h 127.0.0.1 -P 3306 -u root -p flush-hosts

mysqladmin flush-hosts 在 8.4 文档里仍然保留。它需要 performance_schema.host_cache 的 DROP 权限,或 RELOAD 权限。服务器上优先用 TRUNCATE。

运行中把 host_cache_size 改成另一个正数,也会清空缓存并解封,需要 SYSTEM_VARIABLES_ADMIN(旧的 SUPER 也可以)。先 SHOW VARIABLES LIKE 'host_cache_size'; 记下当前值,改完确认客户端能连上之后再设回去。设成 0 会关掉主机缓存:之后不再按 max_connect_errors 封锁主机,每个非本机 TCP 连接都要做一次 DNS 查询。

调大 max_connect_errors

默认值是 100。MySql单节点、主从、双主的构建方法 和 MySql8.4单节点、主从、双主的构建方法 的示例配置里写的也是 100。握手失败很频繁、并且确认 TCP 本身没有持续故障时,可以把阈值调大,减少误封。连接一直在中途被掐断时,调大阈值不能把连接补上。

已经被封锁的主机不会因为阈值变大就自动解开,仍要先做上一节的清空。

先看当前进程里的值:

1
SHOW VARIABLES LIKE 'max_connect_errors';
1
2
3
4
5
+--------------------+-------+
| Variable_name | Value |
+--------------------+-------+
| max_connect_errors | 100 |
+--------------------+-------+

SET GLOBAL 只改当前进程,重启后回到配置文件里的值,没写进配置文件时回到默认 100。

1
2
-- 当前进程立即生效,重启后失效
SET GLOBAL max_connect_errors = 1000;
1
Query OK, 0 rows affected

改完用同一条 SHOW VARIABLES 确认已经变成 1000。SELECT @@GLOBAL.max_connect_errors; 看到的是同一个全局值。

要重启后仍然是 1000,把下面两行放进实际使用的配置文件的 [mysqld] 段,覆盖原来的 max_connect_errors = 100,然后重启 mysqld。

1
2
[mysqld]
max_connect_errors = 1000

SET GLOBAL 改的是当前进程。配置文件在下次启动时生效。两边都改过,并且重启后再查一次,运行值才会和文件一致。

重启后也要保留这个值、my.cnf 保持原样时,用 SET PERSIST。它写的是数据目录下的 mysqld-auto.cnf。用法见 MySql--系统变量。

1
SET PERSIST max_connect_errors = 1000;

做完核对

  • 客户端不再报错误号 1129,能建立新连接。

  • SELECT IP, SUM_CONNECT_ERRORS, COUNT_HOST_BLOCKED_ERRORS FROM performance_schema.host_cache WHERE IP = '刚才那个地址'; 里,这个 IP 不在结果中,或 SUM_CONNECT_ERRORS 为 0。

  • SHOW VARIABLES LIKE 'max_connect_errors'; 与打算保留的值一致。改过配置文件的,重启后再查一次。