MySQL连接数打满的排查路径与实战步骤

描述

 

问题背景

生产环境应用突然报错 “Too many connections”,监控显示 MySQL 连接数达到上限。第一反应是调大 max_connections,但调大后过一段时间又打满了。

连接数打满往往不是因为连接池配置太小,而是有某些连接占着不释放,导致连接池耗尽。这些"僵尸连接"可能来自长时间运行的慢查询、未提交的事务、应用连接泄漏、锁等待、空闲连接超时设置不当等。

盲目扩容 max_connections 不仅解决不了根本问题,还可能导致数据库负载进一步升高,最终拖垮整个数据库。

本文从这个问题出发,梳理 MySQL 连接数问题的完整排查路径,覆盖慢查询、锁等待、未提交事务、连接泄漏、空闲连接、连接池配置、网络问题等常见场景,并给出每个场景的判断方法、修复步骤、风险控制和验证方式。

适用场景

  • 生产环境 MySQL 连接数打满,应用无法建立新连接
  • 连接数持续增长,达到 max_connections 上限
  • 扩容 max_connections 后仍然打满
  • 连接数居高不下,但业务流量不高
  • 大量连接处于 Sleep 状态
  • 大量连接处于 Locked 或 Waiting for table metadata lock 状态
  • 应用连接池频繁报错 “Timeout waiting for connection”
  • 需要定位哪个应用或用户占用最多连接
  • 需要优化连接池配置和超时参数

核心知识点

MySQL 连接机制

MySQL 使用线程模型处理连接,每个连接对应一个线程(在线程池模式下例外)。

连接的生命周期:

  1. 客户端发起连接请求
  2. MySQL 创建新线程(或从线程池分配)
  3. 认证通过后,连接进入 Sleep 状态
  4. 客户端发送 SQL,连接进入 Query 或 Execute 状态
  5. SQL 执行完成,连接回到 Sleep 状态
  6. 客户端关闭连接,或者达到超时时间,MySQL 回收线程

连接数相关参数:

  • max_connections:最大连接数,默认 151
  • max_user_connections:单个用户最大连接数,默认 0(不限制)
  • max_connect_errors:单个主机最大连接错误次数,达到后拒绝连接
  • connect_timeout:连接握手超时时间,默认 10 秒
  • wait_timeout:非交互式连接空闲超时时间,默认 28800 秒(8 小时)
  • interactive_timeout:交互式连接空闲超时时间,默认 28800 秒(8 小时)

连接状态

通过 SHOW PROCESSLIST 可以看到连接状态,常见状态:

  • Sleep:空闲状态,等待客户端发送下一个 SQL
  • Query:正在执行 SQL
  • Locked:等待表锁
  • Waiting for table metadata lock:等待元数据锁
  • Sending data:正在读取和发送数据
  • Sorting result:正在排序
  • Creating tmp table:正在创建临时表
  • Copying to tmp table:正在复制数据到临时表
  • Writing to net:正在发送数据到客户端
  • Waiting for commit lock:等待提交锁
  • Killed:连接被 kill,正在清理
  • Init:连接正在初始化

问题连接通常处于以下状态:

  • Sleep 且时间很长:空闲连接未释放
  • Query 且时间很长:慢查询或长事务
  • Locked:等待锁,可能被其他事务阻塞
  • Waiting for table metadata lock:等待元数据锁,可能有未提交的事务持有锁

连接泄漏

连接泄漏是指应用从连接池获取连接后,没有正确释放,导致连接一直被占用。

常见原因:

  • 代码中获取连接后没有关闭
  • 异常处理不当,finally 块中没有关闭连接
  • 事务未提交或回滚
  • 长时间运行的查询或事务
  • 应用重启或崩溃,连接未正常关闭

连接池

连接池是应用和数据库之间的连接管理层,可以复用连接,减少连接开销。

连接池参数:

  • minimumIdle:最小空闲连接数
  • maximumPoolSize:最大连接数
  • connectionTimeout:获取连接超时时间
  • idleTimeout:空闲连接超时时间
  • maxLifetime:连接最大生命周期
  • validationTimeout:连接验证超时时间

连接池配置不当会导致:

  • 连接池太小,并发高时无法满足需求
  • 连接池太大,占用过多数据库连接
  • 空闲连接超时时间太长,占用数据库连接
  • 连接验证不及时,使用失效连接导致报错

锁等待

MySQL 的锁机制包括表锁、行锁、元数据锁(MDL)等。

锁等待会导致连接被阻塞,无法释放,最终导致连接数打满。

常见锁等待场景:

  • DDL 操作(ALTER TABLE)持有元数据锁,阻塞 DML 操作
  • 长事务持有行锁,阻塞其他事务
  • 大批量 UPDATE 或 DELETE 操作持有行锁
  • 表锁(LOCK TABLES)未释放

慢查询和长事务

慢查询和长事务会长时间占用连接,导致连接无法释放。

慢查询通常是因为:

  • 没有合适的索引
  • 查询逻辑复杂,扫描大量数据
  • 表数据量大,未分页
  • 统计类查询,需要全表扫描

长事务通常是因为:

  • 应用逻辑问题,事务未提交或回滚
  • 事务中包含慢查询
  • 事务中包含外部调用(HTTP、RPC)
  • 事务中包含用户交互,等待用户操作

整体排查思路

MySQL 连接数问题排查分为以下步骤:

  1. 查看当前连接数和最大连接数配置
  2. 列出所有连接,按状态、时间、用户分组统计
  3. 找出占用连接最多的应用或用户
  4. 检查慢查询和长时间运行的 SQL
  5. 检查锁等待和元数据锁
  6. 检查未提交的事务
  7. 检查空闲连接和连接超时配置
  8. 检查应用连接池配置
  9. 检查网络问题和连接错误
  10. 根据排查结果优化或清理连接
  11. 验证连接数是否恢复正常

实战步骤

第一步:查看当前连接数

登录 MySQL,查看当前连接数和最大连接数配置:


			   sqlSHOW STATUS LIKE 'Threads_connected'; SHOW STATUS LIKE 'Max_used_connections'; SHOW VARIABLES LIKE 'max_connections'; 

示例输出:


			   +-------------------+-------+ | Variable_name     | Value | +-------------------+-------+ | Threads_connected | 145   | +-------------------+-------+ +----------------------+-------+ | Variable_name        | Value | +----------------------+-------+ | Max_used_connections | 150   | +----------------------+-------+ +-----------------+-------+ | Variable_name   | Value | +-----------------+-------+ | max_connections | 151   | +-----------------+-------+ 

说明:

  • Threads_connected:当前连接数 145
  • Max_used_connections:历史最大连接数 150
  • max_connections:最大连接数配置 151

如果 Threads_connected 接近 max_connections,说明连接数已接近上限。

如果 Max_used_connections 等于 max_connections,说明曾经打满过。

查看连接数历史趋势:


			   sqlSHOW GLOBAL STATUS LIKE 'Threads%'; 

输出:


			   +-------------------+-------+ | Variable_name     | Value | +-------------------+-------+ | Threads_cached    | 8     | | Threads_connected | 145   | | Threads_created   | 1523  | | Threads_running   | 12    | +-------------------+-------+ 

说明:

  • Threads_cached:线程缓存中的线程数
  • Threads_connected:当前连接数
  • Threads_created:累计创建的线程数
  • Threads_running:正在执行 SQL 的线程数

如果 Threads_created 很大,说明连接频繁创建和销毁,可能需要优化连接池。

如果 Threads_running 很大,说明有很多 SQL 正在执行,可能存在慢查询。

第二步:列出所有连接

使用 SHOW PROCESSLIST 或 SHOW FULL PROCESSLIST 查看所有连接:


			   sqlSHOW FULL PROCESSLIST; 

示例输出:


			   +-----+------+-----------+------+---------+------+-------+------------------+ | Id  | User | Host      | db   | Command | Time | State | Info             | +-----+------+-----------+------+---------+------+-------+------------------+ | 100 | app  | 10.0.1.5  | db1  | Sleep   | 3600 |       | NULL             | | 101 | app  | 10.0.1.5  | db1  | Query   | 120  | Sending data | SELECT * FROM ... | | 102 | app  | 10.0.1.6  | db1  | Sleep   | 1800 |       | NULL             | | 103 | app  | 10.0.1.6  | db1  | Query   | 300  | Locked | UPDATE ... | | 104 | root | localhost | NULL | Query   | 0    | init  | SHOW FULL PROCESSLIST | +-----+------+-----------+------+---------+------+-------+------------------+ 

输出说明:

  • Id:连接 ID
  • User:用户名
  • Host:客户端 IP 和端口
  • db:当前数据库
  • Command:命令类型(Query、Sleep、Connect 等)
  • Time:当前状态持续时间(秒)
  • State:连接状态
  • Info:正在执行的 SQL(部分内容,完整内容需要 SHOW FULL PROCESSLIST)

如果输出太多,可以查询 information_schema.PROCESSLIST 表:


			   sqlSELECT      ID,     USER,     HOST,     DB,     COMMAND,     TIME,     STATE,     LEFT(INFO, 100AS INFO FROM information_schema.PROCESSLIST ORDER BY TIME DESC; 

第三步:按状态、时间、用户统计连接

统计各状态的连接数:


			   sqlSELECT      COMMAND,     COUNT(*AS count FROM information_schema.PROCESSLIST GROUP BY COMMAND ORDER BY count DESC; 

示例输出:


			   +---------+-------+ | COMMAND | count | +---------+-------+ | Sleep   | 120   | | Query   | 25    | +---------+-------+ 

说明大部分连接处于 Sleep 状态,可能是空闲连接未释放。

统计各用户的连接数:


			   sqlSELECT      USER,     COUNT(*AS count FROM information_schema.PROCESSLIST GROUP BY USER ORDER BY count DESC; 

示例输出:


			   +------+-------+ | USER | count | +------+-------+ | app  | 135   | | root | 5     | | monitor | 5  | +------+-------+ 

说明 app 用户占用了 135 个连接。

统计各客户端 IP 的连接数:


			   sqlSELECT      SUBSTRING_INDEX(HOST, ':'1AS client_ip,     COUNT(*AS count FROM information_schema.PROCESSLIST GROUP BY client_ip ORDER BY count DESC; 

示例输出:


			   +------------+-------+ | client_ip  | count | +------------+-------+ | 10.0.1.5   | 80    | | 10.0.1.6   | 55    | | localhost  | 10    | +------------+-------+ 

说明 10.0.1.5 这台服务器占用了 80 个连接。

统计长时间运行的连接:


			   sqlSELECT      ID,     USER,     HOST,     DB,     COMMAND,     TIME,     STATE,     LEFT(INFO, 100AS INFO FROM information_schema.PROCESSLIST WHERE TIME > 300 ORDER BY TIME DESC; 

这会列出运行时间超过 5 分钟的连接。

第四步:检查慢查询

查找正在执行的慢查询:


			   sqlSELECT      ID,     USER,     HOST,     DB,     TIME,     STATE,     INFO FROM information_schema.PROCESSLIST WHERE COMMAND = 'Query'   AND TIME > 10 ORDER BY TIME DESC; 

示例输出:


			   +-----+------+----------+------+------+---------------+------------------+ | ID  | USER | HOST     | DB   | TIME | STATE         | INFO             | +-----+------+----------+------+------+---------------+------------------+ | 101 | app  | 10.0.1.5 | db1  | 120  | Sending data  | SELECT * FROM large_table WHERE ... | | 105 | app  | 10.0.1.6 | db1  | 80   | Copying to tmp table | SELECT ... JOIN ... | +-----+------+----------+------+------+---------------+------------------+ 

说明:

  • ID 101 的查询已经运行了 120 秒,状态是 Sending data
  • ID 105 的查询已经运行了 80 秒,状态是 Copying to tmp table,说明在创建临时表

对于慢查询,可以执行 EXPLAIN 分析执行计划:


			   sqlEXPLAIN SELECT * FROM large_table WHERE ...; 

查看是否缺少索引或全表扫描。

如果慢查询正在执行且影响业务,可以 kill 掉:


			   sqlKILL <connection_id>; 

高风险提醒:

  • KILL 会立即终止连接,如果是正在执行的事务,会回滚
  • 确认 SQL 是否可以安全终止
  • 如果是 DDL 操作,kill 后可能导致表损坏(MySQL 5.6 及以下)
  • 可以先用 KILL QUERY 只终止 SQL,不关闭连接

			   sqlKILL QUERY <connection_id>; 

第五步:检查锁等待

查看当前锁等待情况:

MySQL 5.7 及以上版本:


			   sqlSELECT      r.trx_id AS waiting_trx_id,     r.trx_mysql_thread_id AS waiting_thread,     r.trx_query AS waiting_query,     b.trx_id AS blocking_trx_id,     b.trx_mysql_thread_id AS blocking_thread,     b.trx_query AS blocking_query FROM information_schema.INNODB_LOCK_WAITS w INNER JOIN information_schema.INNODB_TRX b ON b.trx_id = w.blocking_trx_id INNER JOIN information_schema.INNODB_TRX r ON r.trx_id = w.requesting_trx_id; 

MySQL 8.0 及以上版本,表名有变化:


			   sqlSELECT      waiting_pid AS waiting_thread,     waiting_query,     blocking_pid AS blocking_thread,     blocking_query FROM sys.innodb_lock_waits; 

示例输出:


			   +----------------+------------------+------------------+-------------------+ | waiting_thread | waiting_query    | blocking_thread  | blocking_query    | +----------------+------------------+------------------+-------------------+ | 103            | UPDATE ...       | 101              | UPDATE ...        | | 106            | UPDATE ...       | 101              | UPDATE ...        | +----------------+------------------+------------------+-------------------+ 

说明:

  • 线程 103 和 106 正在等待锁
  • 它们都被线程 101 阻塞

找到阻塞线程后,查看其详情:


			   sqlSELECT      trx_id,     trx_state,     trx_started,     trx_requested_lock_id,     trx_wait_started,     trx_weight,     trx_mysql_thread_id,     trx_query FROM information_schema.INNODB_TRX WHERE trx_mysql_thread_id = 101; 

如果阻塞线程是长事务或慢查询,可以 kill 掉:


			   sqlKILL 101; 

高风险提醒:

  • kill 阻塞线程会回滚其事务,可能影响业务
  • 确认该事务是否可以安全回滚
  • 可以先联系应用负责人确认

第六步:检查元数据锁(MDL)

元数据锁是 MySQL 5.5 引入的机制,用于保护表结构。

DDL 操作(ALTER TABLE、DROP TABLE)会持有排他的元数据锁,阻塞所有 DML 操作。

查看元数据锁等待(MySQL 5.7+):


			   sqlSELECT      locked_schema,     locked_table,     locked_type,     waiting_processlist_id,     waiting_age,     waiting_query,     blocking_processlist_id FROM sys.schema_table_lock_waits; 

示例输出:


			   +---------------+--------------+-------------+-----------------------+------------+----------------+------------------------+ | locked_schema | locked_table | locked_type | waiting_processlist_id| waiting_age| waiting_query  | blocking_processlist_id| +---------------+--------------+-------------+-----------------------+------------+----------------+------------------------+ | db1           | users        | EXCLUSIVE   | 110                   | 0015   | ALTER TABLE ...| 105                    | +---------------+--------------+-------------+-----------------------+------------+----------------+------------------------+ 

说明:

  • 线程 110 正在等待表 users 的排他锁
  • 它被线程 105 阻塞

查看阻塞线程:


			   sqlSELECT * FROM information_schema.PROCESSLIST WHERE ID = 105; 

如果阻塞线程是未提交的事务,可以 kill 掉。

如果阻塞线程是正在执行的 DDL,需要等待其完成或 kill 掉(有风险)。

第七步:检查未提交的事务

长时间未提交的事务会持有锁,阻塞其他事务,导致连接堆积。

查看当前所有事务:


			   sqlSELECT      trx_id,     trx_state,     trx_started,     trx_rows_locked,     trx_rows_modified,     trx_mysql_thread_id,     LEFT(trx_query, 100AS trx_query FROM information_schema.INNODB_TRX ORDER BY trx_started; 

示例输出:


			   +--------+-----------+---------------------+-----------------+-------------------+---------------------+------------------+ | trx_id | trx_state | trx_started         | trx_rows_locked | trx_rows_modified | trx_mysql_thread_id | trx_query        | +--------+-----------+---------------------+-----------------+-------------------+---------------------+------------------+ | 12345  | RUNNING   | 2026-09-07 1000 | 1500            | 0                 | 101                 | NULL             | | 12346  | RUNNING   | 2026-09-07 1000 | 0               | 100               | 103                 | UPDATE ...       | +--------+-----------+---------------------+-----------------+-------------------+---------------------+------------------+ 

说明:

  • 事务 12345 从 10:00 开始,已经运行了很长时间,锁定了 1500 行,但没有修改数据,trx_query 为 NULL 说明当前没有执行 SQL
  • 事务 12346 从 10:30 开始,修改了 100 行

如果事务长时间未提交,可能是:

  • 应用逻辑问题,事务中有外部调用或用户交互
  • 应用崩溃或重启,事务未提交
  • 网络问题,连接断开但事务未回滚

对于长时间未提交的事务,可以 kill 掉:


			   sqlKILL 101; 

第八步:检查空闲连接

大量空闲连接会占用数据库连接,导致连接数打满。

查找空闲时间超过 1 小时的连接:


			   sqlSELECT      ID,     USER,     HOST,     DB,     COMMAND,     TIME,     STATE FROM information_schema.PROCESSLIST WHERE COMMAND = 'Sleep'   AND TIME > 3600 ORDER BY TIME DESC; 

示例输出:


			   +-----+------+----------+------+---------+------+-------+ | ID  | USER | HOST     | DB   | COMMAND | TIME | STATE | +-----+------+----------+------+---------+------+-------+ | 100 | app  | 10.0.1.5 | db1  | Sleep   | 7200 |       | | 102 | app  | 10.0.1.6 | db1  | Sleep   | 5400 |       | +-----+------+----------+------+---------+------+-------+ 

说明有连接空闲了 2 小时和 1.5 小时。

可以批量 kill 空闲连接:


			   sqlSELECT      CONCAT('KILL ', ID, ';'AS kill_command FROM information_schema.PROCESSLIST WHERE COMMAND = 'Sleep'   AND TIME > 3600   AND USER != 'root'; 

这会生成 kill 命令,复制执行即可。

高风险提醒:

  • kill 空闲连接可能影响应用连接池
  • 如果应用有连接池,kill 后连接池会重新创建连接
  • 如果应用没有连接池或连接池配置不当,kill 后可能导致应用报错
  • 建议先联系应用负责人确认

第九步:检查连接超时配置

MySQL 的连接超时配置影响空闲连接的回收。

查看超时配置:


			   sqlSHOW VARIABLES LIKE '%timeout%'; 

重点关注:

  • wait_timeout:非交互式连接空闲超时时间,默认 28800 秒(8 小时)
  • interactive_timeout:交互式连接空闲超时时间,默认 28800 秒(8 小时)
  • connect_timeout:连接握手超时时间,默认 10 秒
  • net_read_timeout:读取超时时间,默认 30 秒
  • net_write_timeout:写入超时时间,默认 60 秒

如果 wait_timeout 设置过大(如 8 小时),空闲连接会长时间占用,建议调整为 300-600 秒(5-10 分钟)。

临时修改:


			   sqlSET GLOBAL wait_timeout = 600; SET GLOBAL interactive_timeout = 600; 

永久修改,编辑 /etc/my.cnf:


			   ini[mysqld] wait_timeout = 600 interactive_timeout = 600 

重启 MySQL:


			   bashsystemctl restart mysql 

高风险提醒:

  • 修改超时时间会影响所有连接
  • 如果应用长时间不发送 SQL,连接会被断开
  • 应用需要有连接重连机制
  • 建议先在测试环境验证

第十步:检查应用连接池配置

应用连接池配置不当是连接数打满的常见原因。

常见问题:

  • 连接池最大连接数设置过大,超过数据库 max_connections
  • 连接池空闲连接超时时间过长,占用数据库连接
  • 连接池没有连接验证,使用失效连接导致报错
  • 多个应用实例共享数据库,总连接数超过 max_connections

以 HikariCP(Java)为例,推荐配置:


			   yamlspring:   datasource:     hikari:       minimum-idle: 10       maximum-pool-size: 20       connection-timeout: 30000       idle-timeout: 600000       max-lifetime: 1800000       connection-test-query: SELECT 1 

配置说明:

  • minimum-idle:最小空闲连接数 10
  • maximum-pool-size:最大连接数 20
  • connection-timeout:获取连接超时时间 30 秒
  • idle-timeout:空闲连接超时时间 10 分钟
  • max-lifetime:连接最大生命周期 30 分钟
  • connection-test-query:连接验证 SQL

连接池配置建议:

  • 单个应用实例连接池不超过 20-50
  • 多个实例总连接数不超过数据库 max_connections 的 80%
  • 空闲连接超时时间设置为 5-10 分钟
  • 连接最大生命周期设置为 30-60 分钟
  • 开启连接验证,避免使用失效连接

第十一步:检查网络问题

网络问题会导致连接异常,无法正常释放。

查看连接错误:


			   sqlSHOW GLOBAL STATUS LIKE 'Aborted%'; 

示例输出:


			   +------------------+-------+ | Variable_name    | Value | +------------------+-------+ | Aborted_clients  | 150   | | Aborted_connects | 50    | +------------------+-------+ 

说明:

  • Aborted_clients:客户端异常断开的连接数
  • Aborted_connects:连接握手失败的次数

如果这两个值很大,说明存在网络问题或客户端异常。

常见原因:

  • 网络不稳定,连接超时
  • 应用异常崩溃,连接未正常关闭
  • 防火墙或安全组规则问题
  • 连接数超过系统文件描述符限制

查看系统文件描述符限制:


			   bashulimit -n cat /proc/sys/fs/file-max 

如果限制太小,需要调整。

第十二步:优化 max_connections

如果确认是并发量大导致连接不够,可以适当调大 max_connections

查看当前配置:


			   sqlSHOW VARIABLES LIKE 'max_connections'; 

临时修改:


			   sqlSET GLOBAL max_connections = 500; 

永久修改,编辑 /etc/my.cnf:


			   ini[mysqld] max_connections = 500 

重启 MySQL:


			   bashsystemctl restart mysql 

高风险提醒:

  • 每个连接会占用内存(约 256KB-1MB),连接数过大会导致内存不足
  • 连接数过大会增加数据库负载,影响性能
  • 建议先优化慢查询、锁等待、连接泄漏等问题,而不是盲目扩容
  • 如果确实需要扩容,建议配合监控和告警,观察内存和负载变化
  • MySQL 连接数上限受系统文件描述符限制,需要同步调整

第十三步:调整系统文件描述符限制

MySQL 连接数受系统文件描述符限制。

查看当前限制:


			   bashulimit -n 

如果输出是 1024,说明限制较低。

修改限制:

编辑 /etc/security/limits.conf:


			   mysql soft nofile 65535 mysql hard nofile 65535 

或者修改 systemd 服务配置:

编辑 /etc/systemd/system/mysql.service.d/override.conf:


			   ini[Service] LimitNOFILE=65535 

重载配置并重启 MySQL:


			   bashsystemctl daemon-reload systemctl restart mysql 

验证:


			   bashcat /proc/$(pgrep mysqld)/limits | grep "open files" 

第十四步:监控连接数趋势

建立连接数监控,及时发现问题。

使用 Prometheus 监控 MySQL 连接数:

安装 mysqld_exporter:


			   bashwget https://github.com/prometheus/mysqld_exporter/releases/download/v0.15.0/mysqld_exporter-0.15.0.linux-amd64.tar.gz tar xzf mysqld_exporter-0.15.0.linux-amd64.tar.gz cd mysqld_exporter-0.15.0.linux-amd64 

创建 MySQL 监控用户:


			   sqlCREATE USER 'exporter'@'localhost' IDENTIFIED BY 'password'; GRANT PROCESS, REPLICATION CLIENT, SELECT ON *.* TO 'exporter'@'localhost'; FLUSH PRIVILEGES; 

创建配置文件 .my.cnf:


			   ini[client] user=exporter password=password 

启动 mysqld_exporter:


			   bash./mysqld_exporter --config.my-cnf=.my.cnf & 

Prometheus 配置:


			   yamlscrape_configs:   - job_name: 'mysql'     static_configs:       - targets: ['localhost:9104'] 

Grafana 监控指标:

  • mysql_global_status_threads_connected:当前连接数
  • mysql_global_variables_max_connections:最大连接数
  • mysql_global_status_threads_running:正在执行 SQL 的线程数
  • mysql_global_status_aborted_connects:连接失败次数
  • mysql_global_status_aborted_clients:客户端异常断开次数

告警规则:


			   yamlgroups:   - name: mysql_alerts     rules:       - alert: MySQLTooManyConnections         expr: mysql_global_status_threads_connected / mysql_global_variables_max_connections > 0.8         for: 5m         annotations:           summary: "MySQL connection usage is over 80%"              - alert: MySQLHighThreadsRunning         expr: mysql_global_status_threads_running > 50         for: 5m         annotations:           summary: "MySQL has too many running threads" 

常用命令汇总

连接数检查


			   sql-- 查看当前连接数 SHOW STATUS LIKE 'Threads_connected'; -- 查看历史最大连接数 SHOW STATUS LIKE 'Max_used_connections'; -- 查看最大连接数配置 SHOW VARIABLES LIKE 'max_connections'; -- 查看线程状态 SHOW GLOBAL STATUS LIKE 'Threads%'; -- 列出所有连接 SHOW FULL PROCESSLIST; -- 查询连接详情 SELECT * FROM information_schema.PROCESSLIST; 

连接统计


			   sql-- 按状态统计 SELECT COMMAND, COUNT(*AS count FROM information_schema.PROCESSLIST GROUP BY COMMAND ORDER BY count DESC; -- 按用户统计 SELECT USERCOUNT(*AS count FROM information_schema.PROCESSLIST GROUP BY USER ORDER BY count DESC; -- 按客户端 IP 统计 SELECT SUBSTRING_INDEX(HOST, ':'1AS client_ip, COUNT(*AS count FROM information_schema.PROCESSLIST GROUP BY client_ip ORDER BY count DESC; -- 统计长时间运行的连接 SELECT COUNT(*AS count FROM information_schema.PROCESSLIST WHERE TIME > 300; 

慢查询检查


			   sql-- 查找正在执行的慢查询 SELECT ID, USER, HOST, DB, TIME, STATE, INFO FROM information_schema.PROCESSLIST WHERE COMMAND = 'Query'   AND TIME > 10 ORDER BY TIME DESC; -- 查看慢查询日志配置 SHOW VARIABLES LIKE 'slow_query%'; SHOW VARIABLES LIKE 'long_query_time'; 

锁等待检查


			   sql-- 查看锁等待(MySQL 8.0+) SELECT * FROM sys.innodb_lock_waits; -- 查看所有事务 SELECT trx_id, trx_state, trx_started, trx_rows_locked, trx_rows_modified, trx_mysql_thread_id, LEFT(trx_query, 100AS trx_query FROM information_schema.INNODB_TRX ORDER BY trx_started; -- 查看元数据锁等待(MySQL 5.7+) SELECT * FROM sys.schema_table_lock_waits; 

空闲连接检查


			   sql-- 查找空闲时间超过 1 小时的连接 SELECT ID, USER, HOST, DB, COMMAND, TIME, STATE FROM information_schema.PROCESSLIST WHERE COMMAND = 'Sleep'   AND TIME > 3600 ORDER BY TIME DESC; -- 生成批量 kill 命令 SELECT CONCAT('KILL ', ID, ';'AS kill_command FROM information_schema.PROCESSLIST WHERE COMMAND = 'Sleep'   AND TIME > 3600   AND USER != 'root'; 

连接管理


			   sql-- Kill 连接 KILL <connection_id>; -- Kill 查询(不关闭连接) KILL QUERY <connection_id>; -- 修改连接超时时间 SET GLOBAL wait_timeout = 600; SET GLOBAL interactive_timeout = 600; -- 修改最大连接数 SET GLOBAL max_connections = 500; 

连接错误检查


			   sql-- 查看连接错误 SHOW GLOBAL STATUS LIKE 'Aborted%'; SHOW GLOBAL STATUS LIKE 'Connection_errors%'; -- 查看连接超时配置 SHOW VARIABLES LIKE '%timeout%'; 

配置示例

MySQL 连接配置

/etc/my.cnf:


			   ini[mysqld] # 最大连接数 max_connections = 500 # 单个用户最大连接数(0 表示不限制) max_user_connections = 0 # 连接超时时间 wait_timeout = 600 interactive_timeout = 600 connect_timeout = 10 # 网络超时时间 net_read_timeout = 30 net_write_timeout = 60 # 最大连接错误次数 max_connect_errors = 1000 # 线程缓存大小 thread_cache_size = 50 # 慢查询日志 slow_query_log = 1 slow_query_log_file = /var/log/mysql/slow.log long_query_time = 2 log_queries_not_using_indexes = 0 

配置说明:

  • max_connections:最大连接数,根据业务并发量和服务器资源调整
  • wait_timeout:空闲连接超时时间,建议 5-10 分钟
  • thread_cache_size:线程缓存大小,减少线程创建开销
  • slow_query_log:开启慢查询日志,用于排查慢查询

重启 MySQL:


			   bashsystemctl restart mysql 

HikariCP 连接池配置(Java)

application.yml:


			   yamlspring:   datasource:     driver-class-name: com.mysql.cj.jdbc.Driver     url: jdbc//localhost:3306/db1?useSSL=false&serverTimezone=UTC     username: app     password: password     hikari:       # 最小空闲连接数       minimum-idle: 10       # 最大连接数       maximum-pool-size: 20       # 获取连接超时时间(毫秒)       connection-timeout: 30000       # 空闲连接超时时间(毫秒,10 分钟)       idle-timeout: 600000       # 连接最大生命周期(毫秒,30 分钟)       max-lifetime: 1800000       # 连接验证 SQL       connection-test-query: SELECT 1       # 连接池名称       pool-name: HikariPool 

Druid 连接池配置(Java)

application.yml:


			   yamlspring:   datasource:     driver-class-name: com.mysql.cj.jdbc.Driver     url: jdbc//localhost:3306/db1?useSSL=false&serverTimezone=UTC     username: app     password: password     type: com.alibaba.druid.pool.DruidDataSource     druid:       # 初始化连接数       initial-size: 10       # 最小空闲连接数       min-idle: 10       # 最大连接数       max-active: 20       # 获取连接超时时间(毫秒)       max-wait: 60000       # 空闲连接回收时间(毫秒,10 分钟)       time-between-eviction-runs-millis: 60000       # 连接最小空闲时间(毫秒,30 分钟)       min-evictable-idle-time-millis: 1800000       # 连接验证 SQL       validation-query: SELECT 1       # 申请连接时执行验证       test-while-idle: true       test-on-borrow: false       test-on-return: false 

Python 连接池配置(PyMySQL + DBUtils)


			   pythonfrom DBUtils.PooledDB import PooledDB import pymysql pool = PooledDB(     creator=pymysql,     maxconnections=20,      # 最大连接数     mincached=10,           # 最小空闲连接数     maxcached=15,           # 最大空闲连接数     blocking=True,          # 连接池满时是否阻塞     maxusage=None,          # 单个连接最大使用次数     setsession=[],          # 连接建立时执行的 SQL     ping=1,                 # 连接验证(0=never, 1=default, 2=when used, 4=when connect)     host='localhost',     port=3306,     user='app',     password='password',     database='db1',     charset='utf8mb4' ) # 使用连接 conn = pool.connection() cursor = conn.cursor() cursor.execute("SELECT 1") cursor.close() conn.close() 

监控脚本

/usr/local/bin/mysql-connection-monitor.sh:


			   bash#!/bin/bash MYSQL_USER="root" MYSQL_PASSWORD="password" THRESHOLD=80 # 查询当前连接数和最大连接数 CURRENT=$(mysql -u"$MYSQL_USER" -p"$MYSQL_PASSWORD" -e "SHOW STATUS LIKE 'Threads_connected';" | awk 'NR==2 {print $2}') MAX=$(mysql -u"$MYSQL_USER" -p"$MYSQL_PASSWORD" -e "SHOW VARIABLES LIKE 'max_connections';" | awk 'NR==2 {print $2}') # 计算使用率 USAGE=$((CURRENT * 100 / MAX)) if [ "$USAGE" -ge "$THRESHOLD" ]; then     echo "$(date): WARNING - MySQL connection usage is ${USAGE}% (${CURRENT}/${MAX})" | tee -a /var/log/mysql-connection-monitor.log          # 列出连接统计     echo "Connection statistics:" | tee -a /var/log/mysql-connection-monitor.log     mysql -u"$MYSQL_USER" -p"$MYSQL_PASSWORD" -e "         SELECT              COMMAND,             COUNT(*) AS count         FROM information_schema.PROCESSLIST         GROUP BY COMMAND         ORDER BY count DESC;     " | tee -a /var/log/mysql-connection-monitor.log          # 可以在这里添加告警通知     # curl -X POST https://alert.example.com/api/alert      #     -d "message=MySQL connection usage is ${USAGE}%" fi 

添加到 crontab,每 5 分钟执行一次:


			   bashecho "*/5 * * * * /usr/local/bin/mysql-connection-monitor.sh" | crontab - 

日志和指标观察

MySQL 连接数指标

通过 SHOW STATUS 查看连接数指标:


			   sqlSHOW GLOBAL STATUS LIKE 'Threads%'; SHOW GLOBAL STATUS LIKE 'Max_used_connections'; SHOW GLOBAL STATUS LIKE 'Aborted%'; SHOW GLOBAL STATUS LIKE 'Connection_errors%'; 

关键指标:

  • Threads_connected:当前连接数
  • Threads_running:正在执行 SQL 的线程数
  • Max_used_connections:历史最大连接数
  • Aborted_clients:客户端异常断开次数
  • Aborted_connects:连接握手失败次数
  • Connection_errors_max_connections:因 max_connections 限制被拒绝的连接数

慢查询日志

查看慢查询日志配置:


			   sqlSHOW VARIABLES LIKE 'slow_query%'; SHOW VARIABLES LIKE 'long_query_time'; 

分析慢查询日志:


			   bash# 使用 mysqldumpslow 分析 mysqldumpslow -s t -t 10 /var/log/mysql/slow.log # 使用 pt-query-digest 分析(Percona Toolkit) pt-query-digest /var/log/mysql/slow.log 

锁等待日志

开启 InnoDB 锁等待日志:


			   sqlSET GLOBAL innodb_lock_wait_timeout = 50; SET GLOBAL innodb_print_all_deadlocks = ON; 

查看死锁日志:


			   sqlSHOW ENGINE INNODB STATUSG 

在 LATEST DETECTED DEADLOCK 部分可以看到最近的死锁信息。

连接趋势分析

使用脚本记录连接数趋势:


			   bash#!/bin/bash while truedo     CURRENT=$(mysql -uroot -ppassword -e "SHOW STATUS LIKE 'Threads_connected';" | awk 'NR==2 {print $2}')     RUNNING=$(mysql -uroot -ppassword -e "SHOW STATUS LIKE 'Threads_running';" | awk 'NR==2 {print $2}')     echo "$(date '+%Y-%m-%d %H:%M:%S'),$CURRENT,$RUNNING" >> /var/log/mysql-connections.csv     sleep 60 done 

分析 CSV 文件:


			   bash# 查看连接数峰值 sort -t, -k2 -n -r /var/log/mysql-connections.csv | head -n 10 # 绘制趋势图(需要 gnuplot) gnuplot <<EOF set datafile separator "," set xdata time set timefmt "%Y-%m-%d %H:%M:%S" set format x "%H:%M" plot "/var/log/mysql-connections.csv" using 1:2 with lines title "Connections" EOF 

排查路径图


			   MySQL 连接数打满     |     +-- SHOW STATUS --> Threads_connected 接近 max_connections     |                    |     |                    +-- 列出所有连接 SHOW PROCESSLIST     |                              |     |                              +-- 大量 Sleep 连接     |                              |     |     |                              |     +-- 检查空闲时间     |                              |     +-- 检查 wait_timeout 配置     |                              |     +-- 检查应用连接池配置     |                              |     +-- Kill 长时间空闲连接     |                              |     |                              +-- 大量 Query 连接     |                              |     |     |                              |     +-- 检查慢查询     |                              |     +-- EXPLAIN 分析执行计划     |                              |     +-- 优化索引或 SQL     |                              |     +-- Kill 慢查询     |                              |     |                              +-- 大量 Locked 连接     |                                    |     |                                    +-- 检查锁等待     |                                    +-- 找到阻塞线程     |                                    +-- 检查长事务     |                                    +-- Kill 阻塞线程     |     +-- 连接数持续增长                     |                     +-- 检查应用连接泄漏                     +-- 检查应用连接池配置                     +-- 检查网络问题                     +-- 检查连接错误日志 

风险提醒

Kill 连接风险

  • KILL 会立即终止连接,如果是正在执行的事务,会回滚
  • 如果是 DDL 操作,kill 后可能导致表损坏(MySQL 5.6 及以下)
  • 批量 kill 连接可能影响业务
  • kill 前确认连接用途和业务影响
  • 可以先用 KILL QUERY 只终止 SQL,不关闭连接

修改超时时间风险

  • 修改 wait_timeout 会影响所有连接
  • 如果应用长时间不发送 SQL,连接会被断开
  • 应用需要有连接重连机制
  • 建议先在测试环境验证

修改 max_connections 风险

  • 每个连接会占用内存(约 256KB-1MB)
  • 连接数过大会导致内存不足,OOM
  • 连接数过大会增加数据库负载,影响性能
  • 建议先优化慢查询、锁等待、连接泄漏等问题
  • 如果确实需要扩容,建议配合监控和告警

重启 MySQL 风险

  • 重启 MySQL 会断开所有连接,影响业务
  • 应用需要有连接重连机制
  • 重启前确认有维护窗口
  • 重启后验证业务功能

验证方式

连接数验证

清理或优化后,执行:


			   sqlSHOW STATUS LIKE 'Threads_connected'; 

确认连接数下降。

慢查询验证

优化慢查询后:


			   sqlSELECT * FROM information_schema.PROCESSLIST WHERE COMMAND = 'Query' AND TIME > 10; 

确认没有长时间运行的查询。

锁等待验证

处理锁等待后:


			   sqlSELECT * FROM sys.innodb_lock_waits; 

确认没有锁等待。

空闲连接验证

清理空闲连接后:


			   sqlSELECT COUNT(*FROM information_schema.PROCESSLIST WHERE COMMAND = 'Sleep' AND TIME > 3600; 

确认长时间空闲连接已清理。

业务功能验证

优化或清理后,验证业务功能:

  • 应用是否正常连接数据库
  • 应用是否能正常执行 SQL
  • 应用连接池是否正常
  • 监控指标是否正常

回滚方案

配置修改回滚

如果修改配置导致问题:


			   bash# 回滚配置文件 cp /etc/my.cnf.bak /etc/my.cnf # 重启 MySQL systemctl restart mysql 

或者临时修改回原值:


			   sqlSET GLOBAL wait_timeout = 28800; SET GLOBAL max_connections = 151; 

Kill 连接回滚

如果误 kill 连接:

  • 连接无法恢复,但应用连接池会自动重新创建连接
  • 如果应用报错,重启应用
  • 如果是长事务被 kill,事务会回滚,数据不会丢失

生产环境注意事项

操作前检查

  • 确认当前环境:开发、测试、还是生产
  • 确认操作时间窗口,避免业务高峰期
  • 确认有备份和回滚方案
  • 确认有监控和告警
  • 确认操作权限和审批流程

操作中注意

  • 逐步优化,不要一次性修改多个配置
  • kill 连接前确认用途和业务影响
  • 修改配置前先在测试环境验证
  • 记录操作日志和命令历史
  • 保持与业务方的沟通

操作后验证

  • 验证连接数是否恢复正常
  • 验证业务功能是否正常
  • 验证监控指标是否正常
  • 验证应用连接池是否正常
  • 验证慢查询和锁等待是否减少

文档和复盘

  • 记录问题根因和解决过程
  • 更新运维文档和 SOP
  • 总结经验教训
  • 优化监控和告警策略
  • 制定预防措施

预防措施

连接数监控

建立连接数监控告警:

  • 连接数使用率超过 80% 预警
  • 连接数使用率超过 90% 告警
  • 连接数持续增长告警
  • 慢查询数量告警
  • 锁等待数量告警

监控工具:

  • Prometheus + Grafana
  • Zabbix
  • Nagios
  • 云厂商监控

连接池配置优化

  • 单个应用实例连接池不超过 20-50
  • 多个实例总连接数不超过数据库 max_connections 的 80%
  • 空闲连接超时时间设置为 5-10 分钟
  • 连接最大生命周期设置为 30-60 分钟
  • 开启连接验证,避免使用失效连接
  • 使用连接池监控,及时发现连接泄漏

慢查询优化

  • 建立索引,优化查询
  • 避免全表扫描
  • 分页查询大数据量
  • 避免在事务中执行慢查询
  • 定期分析慢查询日志,持续优化

事务管理

  • 事务尽量短,快速提交或回滚
  • 避免在事务中执行外部调用(HTTP、RPC)
  • 避免在事务中等待用户操作
  • 使用乐观锁减少锁等待
  • 定期检查长事务,及时处理

应用设计

  • 使用连接池管理连接
  • 确保连接正确关闭,使用 try-finally 或 with 语句
  • 异常处理中关闭连接
  • 定期检查连接泄漏
  • 使用连接池监控和告警

容量规划

  • 定期评估连接数趋势
  • 根据业务增长预测连接数需求
  • 提前扩容,留出缓冲空间
  • 合理配置 max_connections,不要盲目扩容

总结

MySQL 连接数打满问题排查的核心思路是:

  1. 先用 SHOW STATUS 和 SHOW PROCESSLIST 查看当前连接数和连接状态
  2. 按状态、用户、客户端 IP 统计连接,定位占用最多的来源
  3. 检查慢查询、锁等待、未提交事务、空闲连接等问题连接
  4. 根据排查结果优化或清理:kill 慢查询、kill 阻塞线程、kill 空闲连接
  5. 检查应用连接池配置,优化连接池参数
  6. 检查 MySQL 超时配置,调整 wait_timeout 和 interactive_timeout
  7. 如果确实需要扩容,适当调大 max_connections,但要配合监控和告警
  8. 建立监控告警和自动化巡检机制,预防问题再次发生

在生产环境操作时,务必注意:

  • kill 连接前确认用途和业务影响
  • 修改配置前先在测试环境验证
  • 修改 max_connections 要考虑内存和负载影响
  • 重启 MySQL 前确认有维护窗口
  • 操作后验证业务功能和监控指标
  • 记录操作过程和结果,复盘总结

最重要的是,不要只关注扩容 max_connections,而是要找到根因,优化慢查询、锁等待、连接泄漏等问题,从源头解决问题,建立长效机制。

 


打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分