文章目录
- 说明
- 1. host_cache
- 2. 问题复现
- 2.1 未调用 close()
- 2.2 MySQL 协议握手错误
- 3. 解决方法
说明
前几天收到了研发同学反馈,测试环境的数据库出现了无法连接的情况,并附上了报错,本篇文章分析该异常的原因。
ERROR 1129 (HY000): Host ‘xxxxxx’ is blocked because of many connection errors; unblock with ‘mysqladmin flush-hosts’
1. host_cache
首先,需要介绍一下 host_cache 表,它记录了 MySQL 服务器缓存的连接信息,常用于排查连接错误的问题。
select * from performance_schema.host_cache;
比如常见的用户登陆密码错误:
# 用 Python 代码模拟 1000 次,密码错误登陆
import pymysqldef mysql_coon(query_text: str):with pymysql.connect(host='172.16.104.56',port=3306,user='bing',password='abc23', # 错误的密码charset='utf8',database='information_schema',) as cursor:cursor.execute(query_text)content = cursor.fetchall()return contentfor i in range(1000):try:mysql_coon('select 1')except Exception as info:print(info)
得到的结果:
+----------------+---------------+----------------+-----------------------------+
| IP | HOST | HOST_VALIDATED | COUNT_AUTHENTICATION_ERRORS |
+----------------+---------------+----------------+-----------------------------+
| 192.168.115.44 | NULL | YES | 1000 |
+----------------+---------------+----------------+-----------------------------+
可以看到 COUNT_AUTHENTICATION_ERRORS 字段的值等于 1000,表示该地址发起了 1000 次权限验证错误连接。
2. 问题复现
分析该异常,需要先让问题复现,本小节介绍如何模拟复现。
2.1 未调用 close()
研发在提出问题前,自己也分析了错误日志,发现有很多如下的报错,不知是否与 blocked because of many connection errors 有关联。
[Note] Aborted connection 3642 to db: 'information_schema' user: 'bing' host: '192.168.115.44' (Got an error reading communication packets)
[Note] Aborted connection 3643 to db: 'information_schema' user: 'bing' host: '192.168.115.44' (Got an error reading communication packets)
[Note] Aborted connection 3644 to db: 'information_schema' user: 'bing' host: '192.168.115.44' (Got an error reading communication packets)
经过测试,该异常与 blocked because of many connection errors 无关,出现的原因是客户端没有正确的关闭连接,比如使用代码连接数据库,没有主动调用 close() 方法。
import pymysqldef mysql_coon(query_text: str):with pymysql.connect(host='172.16.104.56',port=3306,user='bing',password='abc123',charset='utf8',database='information_schema',) as cursor:cursor.execute(query_text)content = cursor.fetchall()# 这行注释掉和不注释掉,都试试看# cursor.close()return contentfor i in range(1000):try:mysql_coon('select 1')except Exception as info:print(info)
上面提供了一个 Demo,感兴趣的朋友可以试试看,把 cursor.close()
注释打开和注释状态下都试试,观察 tail -f 错误日志。
2.2 MySQL 协议握手错误
刚开始在网上查该问题,发现大多数都说是如果短时间内连接错误数量大于 max_connect_errors 参数的值,就会抛出异常。
ERROR 1129 (HY000): Host ‘xxxxxx’ is blocked because of many connection errors; unblock with ‘mysqladmin flush-hosts’
通过上方代码,模拟了用户登陆错误,执行 SQL 错误,均未触发该异常,随后在官方文档中 host_cache Table 介绍中,提到了一个 SUM_CONNECT_ERRORS 字段。
SUM_CONNECT_ERRORS 表示被 “阻塞” 的连接错误数量,仅对协议握手错误进行计数,并且仅针对通过验证 ( HOST_VALIDATED = YES) 的主机。一旦 SUM_CONNECT_ERRORS 给定主机达到的值 max_connect_errors,来自该主机的新连接将被阻止。
推荐阅读:25.12.16.1 The host_cache Table
从文档描述中,了解到这里的错误指的是协议握手错误。接下来进行模拟,首先将 max_connect_errors 调整为 2。
set global max_connect_errors = 2;
使用 telnet 命令,对数据库端口进行探测。
# 第一次探测
telnet 172.16.104.56 3306# 第二次探测
telnet 172.16.104.56 3306
此时,查看 host_cache 表中的 SUM_CONNECT_ERRORS 等于 2 达到了 max_connect_errors 上限。
>>>select IP,SUM_CONNECT_ERRORS from host_cache;
+----------------+--------------------+
| IP | SUM_CONNECT_ERRORS |
+----------------+--------------------+
| 172.16.104.57 | 2 |
+----------------+--------------------+
尝试发起连接,错误得到复现。
[root@172-16-104-57 ~]# mysql -h172.16.104.56 -ubing -pabc123
ERROR 1129 (HY000): Host '172.16.104.57' is blocked because of many connection errors; unblock with 'mysqladmin flush-hosts'
上面使用 telnet 来模拟协议握手失败的例子,由于 telnet 只是发送了 TCP 的握手包,并不会发送 MySQL 登录认证包,服务器端等待 10 秒(mysql 的 connect_timeout=10)就关闭了连接,造成 MySQL 协议握手失败。
3. 解决方法
临时解决方法是使用 flush hosts
命令,重新加载一次 host_cache 表,异常计数会被清空,就暂时不会出现报错了。也可以调大 max_connect_errors 参数,建议最大值为 1000。
set global max_connect_errors = 1000;
上面介绍的是临时解决方法,实际遇到此类异常时,建议先查 host_cache 表,找到发起 MySQL 协议握手失败的程序,定位问题的根源,才能彻底解决问题。