暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

揭秘 MySQL Aborted Connection:全面了解并解决连接中断问题!

数据库驾驶舱 2024-07-19
401

症状

有时候,MySQL 错误日志会显示如下的“连接中止”信息:

2024-04-24T17:09:43.867760+08:00 107 [Note] Aborted connection 107 to db: 'unconnected' user'<USER>' host: '<HOST2>' (Got timeout reading communication packets)
2024-04-24T17:10:38.510339+08:00 171 [Note] Aborted connection 171 to db: '<DB>' user'<USER>' host: '<HOST2>' (Got an error reading communication packets)
2024-04-24T17:10:38.510384+08:00 185 [Note] Aborted connection 185 to db: 'unconnected' user'<USER>' host: '<HOST2>' (Got timeout writing communication packets)
2024-04-24T17:10:38.511003+08:00 758 [Note] Aborted connection 758 to db: '<DB>' user'<USER>' host: '<HOST2>' (Got an error writing communication packets)

通常,并不是所有的四种消息类型都会出现。

变化

这些消息可能在以下一种或多种变化后开始出现:

  • 部署新应用或对现有应用进行更改。

  • 降低以下超时选项之一的值:interactive_timeout、net_read_timeout、net_write_timeout、wait_timeout。

  • 增加错误日志的详细程度:

    • 在 MySQL 5.7 及以后版本中设置 log_error_verbosity = 3。

    • 在 MySQL 5.6 及更早版本中设置 log_warnings = 2。

  • 升级到 MySQL Server 5.7。

原因

“连接中止”消息分为两类:超时和错误:

  • 「超时」:这些消息通常是由服务器端设置的 interactive_timeout、net_read_timeout、net_write_timeout、wait_timeout 值引起的。

  • 「错误」:这些消息通常是由连接异常终止或网络数据包格式不正确引起的。

导致这些消息的一些常见原因包括:

  • 连接在空闲状态下因超过 wait_timeout 或 interactive_timeout 秒数(取决于连接是否为交互式)而超时。

  • 连接异常终止,例如应用崩溃或未显式关闭连接。

  • 网络延迟或中断。

  • 行的数据超过客户端的 max_allowed_packet 值。参见参考手册中的 Packet Too Large。

以下将详细讨论四种消息的含义及触发示例。

读取通信数据包超时

此消息主要发生在由于空闲状态(Sleep 状态)超过 wait_timeout 或 interactive_timeout 秒数而导致连接超时的情况下。wait_timeout 设置用于非交互式客户端/应用;interactive_timeout 设置用于交互式客户端,例如在交互模式下使用 mysql 命令行客户端。

当服务器在超过 net_read_timeout 秒数后未收到客户端的任何消息时,也会出现此消息。

由于空闲导致此消息触发的示例如下:

session 1> select @@global.wait_timeout, @@global.interactive_timeout;

+-----------------------+------------------------------+
| @@global.wait_timeout | @@global.interactive_timeout |
+-----------------------+------------------------------+
|                 28800 |                        28800 |
+-----------------------+------------------------------+

1 row in set (0.00 sec)

session 1> set global interactive_timeout=10;

Query OK, 0 rows affected (0.00 sec)

session 1>select @@global.wait_timeout, @@global.interactive_timeout;

+-----------------------+------------------------------+
| @@global.wait_timeout | @@global.interactive_timeout |
+-----------------------+------------------------------+
|                 28800 |                           10 |
+-----------------------+------------------------------+

1 row in set (0.00 sec)

^^^ 已将 interactive_timeout 的全局值更改为 10 秒,这意味着新连接将应用更改后的值。

shell$ mysql -u<USER> --host=<HOST1> --prompt='session 2>'

mysql: [Warning] Using a password on the command line interface can be insecure.

Welcome to the MySQL monitor.  Commands end with ; or \g.

Your MySQL connection id is 7

Server version: 5.7.20-enterprise-commercial-advanced-log MySQL Enterprise Server - Advanced Edition (Commercial)

Copyright (c) 20002017, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its

affiliates. Other names may be trademarks of their respective

owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

session 2>

^^^ 建立了新连接。

session 1> show processlist;

+----+-----------+-------------------+------+---------+------+----------+------------------+
| Id | User      | Host              | db   | Command | Time | State    | Info             |
+----+-----------+-------------------+------+---------+------+----------+------------------+
|  3 | root      | localhost         | NULL | Query   |    0 | starting | SHOW PROCESSLIST |
|  7 | <USER>    | <HOST2>:<PORT1>   | NULL | Sleep   |    6 |          | NULL             |
+----+-----------+-------------------+------+---------+------+----------+------------------+

2 rows in set (0.00 sec)

session 1> show processlist;

+----+-----------+-------------------+------+---------+------+----------+------------------+
| Id | User      | Host              | db   | Command | Time | State    | Info             |
+----+-----------+-------------------+------+---------+------+----------+------------------+
|  3 | root      | localhost         | NULL | Query   |    0 | starting | SHOW PROCESSLIST |
|  7 | <USER>    | <HOST2>:<PORT1>   | NULL | Sleep   |   10 |          | NULL             |
+----+-----------+-------------------+------+---------+------+----------+------------------+

2 rows in set (0.00 sec)

^^^ 连接 id 7 已空闲 10 秒

session 1> show processlist;

+----+------+-----------+------+---------+------+----------+------------------+
| Id | User | Host      | db   | Command | Time | State    | Info             |
+----+------+-----------+------+---------+------+----------+------------------+
|  3 | root | localhost | NULL | Query   |    0 | starting | SHOW PROCESSLIST |
+----+------+-----------+------+---------+------+----------+------------------+

1 row in set (0.00 sec)

^^^ 在空闲 10 秒后,id 7 被 mysqld 终止


然后,在 MySQL 错误日志中记录了中止消息(前提是错误日志记录级别设置得足够高):

2024-06-01T11:20:23.640885+08:00 7230777 [Note] Aborted connection 7 to db: 'unconnected' user: '<USER>' host: '<HOST2>' (**Got timeout reading communication packets**)

读取通信数据包时出错

此消息主要发生在连接异常终止时。以下是一些被认为是连接异常终止的示例:

  • 应用崩溃。

  • 应用正常关闭但未关闭连接。

  • 连接被防火墙或由于 keepalive 设置关闭。

可以通过 kill -9
命令轻松重现此消息:

session 1> SHOW PROCESSLIST;

+----+-----------+-------------------+------+---------+------+----------+------------------+
| Id | User      | Host              | db   | Command | Time | State    | Info             |
+----+-----------+-------------------+------+---------+------+----------+------------------+
|  3 | root      | localhost         | NULL | Query   |    0 | starting | SHOW PROCESSLIST |
|  9 | <USER>    | <HOST2>:<PORT2>   | NULL | Sleep   |    5 || NULL             |
+----+-----------+-------------------+------+---------+------+----------+------------------+

2 rows in set (0.00 sec)

^^^ 连接 id 9 是从 <HOST2> 建立的。

[root@<DB>vm2 ~]# ps -ef | grep <USER>

root      4140  3987  3 00:57 pts/0    00:00:00 /db/5.7/bin/mysql -u<USER> --host=<HOST3> --prompt=session 2>
root      4142  4067  0 00:57 pts/1    00:00:00 grep <USER>

[root@<DB>vm2 ~]# kill -9 4140

^^^ 强制杀死连接进程

然后,在 MySQL 错误日志中记录了中止消息(前提是错误日志记录级别设置得足够高):

2024-07-18T13:56:40.095989+08:00 7230777 [Note] Aborted connection 9 to db: 'unconnected' user: '<USER>' host: '<HOST2>' (**Got an error reading communication packets**)

当超过 max_allowed_packet 设置或某些原因传输了格式错误的网络数据包时,也会出现此消息:

mysql> CREATE TABLE <DB>.<TABLE> (a int unsigned NOT NULL PRIMARY KEY, b varchar(16385));

Query OK, 0 rows affected (0.07 sec)

mysql> INSERT INTO <DB>.<TABLEVALUES (1, REPEAT('a'16300));

Query OK, 1 row affected (0.01 sec)

mysql> INSERT INTO <DB>.<TABLEVALUES (2, REPEAT('a'16300));

Query OK, 1 row affected (0.02 sec)

mysql> INSERT INTO <DB>.<TABLEVALUES (3, REPEAT('a'16385));

Query OK, 1 row affected (0.02 sec)

mysql> SELECT a, LENGTH(b) FROM <DB>.<TABLE>;

+------+-----------+
| a    | LENGTH(b) |
+------+-----------+
|    1 |     16300 |
|    2 |     16300 |
|    3 |     16385 |
+------+-----------+

3 rows in set (0.00 sec)

^^^ 插入了 3 行,每行大约 1630016300 和 16385 字节。

shell$ mysql --max-allowed-packet=16384 -u<USER> --host=<HOST3> -e "SELECT b FROM <DB>.<TABLE> WHERE a = 1" > x

mysql: [WarningUsing a password on the command line interface can be insecure.

^^^ 第一个大约 16300 字节的行被导出,没有错误,因为 max_allowed_packet(16384 字节)大于行。

shell$ mysql --max-allowed-packet=16384 -u<USER> --host=<HOST3>-e "SELECT b FROM <DB>.<TABLE> WHERE a IN (1,2)" > x

^^^ 每行大约 16300 字节的查询没有错误,因为 max_allowed_packet(16384 字节)大于任意一行。

shell$ mysql --max-allowed-packet=16384 -u<USER> --host=<HOST3>-e "SELECT b FROM <DB>.<TABLE> WHERE a = 3" > x

ERROR 2020 (HY000) at line 1: Got packet bigger than 'max_allowed_packet' bytes

^^^ 导出行大于 max_allowed_packet 时发生错误。

然后,在 MySQL 错误日志中记录了中止消息:

2024-07-18T14:19:34.317789+08:00 7230777 [Note] Aborted connection 171 to db: '<DB>' user: '<USER>' host: '<HOST2>' (**Got an error reading communication packets**)

写入通信数据包超时

当网络数据包在超过 net_write_timeout 秒后未到达客户端时,会出现此消息。

当通过 mysqldump 导出大行(例如 blob/text 数据)时,也会出现此消息:MySQL 服务器尝试发送数据,但 mysqldump 写入速度没有 MySQL 服务器发送数据快,因此 MySQL 服务器可能会等待 net_write_timeout 后关闭连接。

一个示例如下:

session 2> connect

Connection id:    165

Current database: *** NONE ***

session 2> SELECT * FROM <DB>.<TABLE>;

^^^ 连接 id 165 建立,然后请求大结果集

session 1> SHOW FULL PROCESSLIST;

+-----+-----------+-------------------+------+---------+------+-------------------+----------------------------+
| Id  | User      | Host              | db   | Command | Time | State             | Info                       |
+-----+-----------+-------------------+------+---------+------+-------------------+----------------------------+
| 165 | <USER>    | <HOST2>:<PORT3>   | NULL | Query   |    1 | Sending to client | SELECT * FROM <DB>.<TABLE> |
| 166 | root      | localhost         | <DB> | Query   |    0 | starting          | SHOW FULL PROCESSLIST      |
+-----+-----------+-------------------+------+---------+------+-------------------+----------------------------+

2 rows in set (0.00 sec)

# 假设 eth3 用于从 <HOST2> 到 MySQL 服务器的连接:

shell$ sudo ifdown eth3

^^^ 关闭用于 session 2 的网络 eth3 接口。

session 1> SHOW FULL PROCESSLIST;

+-----+-----------+-------------------+------+---------+------+-------------------+----------------------------+
| Id  | User      | Host              | db   | Command | Time | State             | Info                       |
+-----+-----------+-------------------+------+---------+------+-------------------+----------------------------+
| 165 | <USER>    | <HOST2>:<PORT3>   | NULL | Query   |   60 | Sending to client | SELECT * FROM <DB>.<TABLE> |
| 166 | root      | localhost         | <DB> | Query   |    0 | starting          | SHOW FULL PROCESSLIST      |
+-----+-----------+-------------------+------+---------+------+-------------------+----------------------------+

2 rows in set (0.00 sec)

session 1> SHOW FULL PROCESSLIST;

+-----+------+-----------+------+---------+------+----------+-----------------------+
| Id  | User | Host      | db   | Command | Time | State    | Info                  |
+-----+------+-----------+------+---------+------+----------+-----------------------+
| 166 | root | localhost | <DB> | Query   |    0 | starting | SHOW FULL PROCESSLIST |
+-----+------+-----------+------+---------+------+----------+-----------------------+
1 row in set (0.00 sec)

^^^ 等待 60 秒后,连接 id 165 被 MySQL 服务器终止。

然后,在 MySQL 错误日志中记录了中止消息(前提是错误日志记录级别设置得足够高):

2024-07-18T13:56:51.815316+08:00 7230777 [Note] Aborted connection 165 to db: 'unconnected' user: '<USER>' host: '<HOST2>' (**Got timeout writing communication packets**)

写入通信数据包时出错

当网络数据包由于连接异常终止或格式错误的网络数据包未到达客户端时,会出现此消息。
此消息的原因与“读取通信数据包时出错”的原因类似。
此消息主要发生在连接异常终止时。以下是一些被认为是连接异常终止的示例:

  • 应用崩溃。

  • 应用正常关闭但未关闭连接。

  • 连接被防火墙或由于 keepalive 设置关闭。

可以通过 kill -9
命令轻松重现此消息:

mysql> SHOW FULL PROCESSLIST;
+--------+------+-------------------+------+---------+------+-------------------+-------------------------------------------+

| Id     | User | Host              | db   | Command | Time | State             | Info                                      |
+--------+------+-------------------+------+---------+------+-------------------+-------------------------------------------+

| 844752 | root | localhost         | <DB> | Query   | 0    | starting          | SHOW FULL PROCESSLIST                     |
| 844760 | root | <HOST2>:<PORT4>   | NULL | Query   | 8    | Sending to client | select * from <DB>.<TABLE1>,<DB>.<TABLE2> |
+--------+------+-------------------+------+---------+------+-------------------+-------------------------------------------+

2 rows in set (0.00 sec)

^^^ 连接 id 844760 是从 <HOST2> 建立的。

shell$ ps -ef | grep mysql
root 10806 9624 60 00:06 pts/0 00:00:03 mysql -uroot -px xx -h <HOST3>00 -e select * from <DB>.<TABLE1>,<DB>.<TABLE2>
root 10816 9964 0 00:06 pts/1 00:00:00 grep mysql

shell$ kill -9 10806

^^^ 强制杀死连接进程

然后,在 MySQL 错误日志中记录了中止消息:

2024-07-18T14:19:34.317789+08:00 7230777 [Note] Aborted connection 844760 to db: 'unconnected' user: 'root' host: '<HOST2>' (Got an error writing communication packets)

当超过 max_allowed_packet 设置或某些原因传输了格式错误的网络数据包时,也会出现此消息:

mysql> CREATE TABLE <DB>.<TABLE3> (c1 longtext);
Query OK, 0 rows affected (0.21 sec)

mysql> INSERT <DB>.<TABLE3> VALUE (REPEAT('abcd',1000000));
Query OK, 1 row affected (1.45 sec)

mysql> SELECT LENGTH(c1) FROM <DB>.<TABLE3>;
+------------+

| LENGTH(c1) |
+------------+

| 4000000    |
+------------+

1 row in set (0.01 sec)

^^^ 插入了 1 行,大约 4000000 字节。

shell$ mysql -uroot -p -h <HOST3>00 --max-allowed-packet=1234567

mysql> USE <DB>
Database changed

mysql> SELECT * FROM <TABLE3>;
ERROR 2020 (HY000): Got packet bigger than 'max_allowed_packet' bytes

^^^ 发生错误,因为 mysql 客户端获取的行大于其 max_allowed_packet。

然后,在 MySQL 错误日志中记录了中止消息:

2024-06-27T15:33:15.594861+08:00 685 [Note] Aborted connection 844763 to db: '<DB>' user: 'root' host: '<HOST2>' (Got an error writing communication packets)

解决方案

一般来说,需要检查应用和网络是否有错误或过载问题。同时,建议阅读参考手册中的 Communication Errors and Aborted Connections。其余部分将讨论一些步骤和想法,以具体解决三种消息的问题。

读取通信数据包超时

要确定为何发生超时消息以及如何避免,请考虑以下因素:

  • interactive_timeout 和/或 wait_timeout 的值是否太小。默认值为 8 小时(28800 秒),但如果您更改了值,可能对于您的工作负载来说太短了。在这种情况下,请增加其中一个或两个值。

  • 如果超时值足够大,请确定为何连接在较长时间内未使用。例如:

    • 如果您使用连接池,池可能太大,可以考虑减少池的大小或让池关闭空闲连接。

    • 长时间运行的应用可能会创建新连接,然后不再使用它们。在这种情况下,请确保应用关闭连接或使用连接池。

  • 检查网络是否有问题。如果网络整体无法跟上,可能需要增加 net_read_timeout 以避免超时。

读取通信数据包时出错

要解决引起 读取通信数据包时出错 的问题,请检查以下事项:

  • 检查应用日志。是否有应用意外重启的迹象?如果有,请调查原因。

  • 验证应用在关闭之前是否显式关闭所有数据库连接。如果没有,请添加关闭连接的代码。具体方法取决于您使用的连接器/API。

  • 检查网络和网络服务(如防火墙)是否可能导致消息无法到达。

  • 如果处理大行(按字节数计),考虑增加服务器端和客户端的 max_allowed_packet,或更改查询以减少每行的数据量(例如,在 SELECT 语句中显式列出要检索的列,而不是使用 SELECT *)。

  • 如果使用连接器,请检查套接字超时时间是否足够长(例如,对于 Connector/J,建议禁用或增加 socketTimeout)。

写入通信数据包超时

检查以下几点以解决写入数据包超时的问题:

  • 检查应用日志以确定它在做什么。如果它在收到数据时忙于执行某个任务(例如 mysqldump 忙于将数据写入磁盘),请调查如何将任务拆分为更小的部分,以便应用不会阻塞。

  • 检查网络是否有问题。

  • 一种选择是增加 net_write_timeout 以允许 MySQL 服务器在超时前等待更长时间。然而,优先解决潜在问题。

写入通信数据包时出错

与_读取通信数据包时出错_的消息类似,检查以下事项以解决此问题:

  • 检查应用日志。是否有应用意外重启的迹象?如果有,请调查原因。

  • 验证应用在关闭之前是否显式关闭所有数据库连接。如果没有,请添加关闭连接的代码。具体方法取决于您使用的连接器/API。

  • 检查网络和网络服务(如防火墙)是否可能导致消息无法到达。

  • 如果处理大行(按字节数计),考虑增加服务器端和客户端的 max_allowed_packet,或更改查询以减少每行的数据量(例如,在 SELECT 语句中显式列出要检索的列,而不是使用 SELECT *)。

  • 如果使用连接器,请检查套接字超时时间是否足够长(例如,对于 Connector/J,建议禁用或增加 socketTimeout)。

「欢迎关注我们的公众号,获取更多技术分享与经验交流。」


文章转载自数据库驾驶舱,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论