生产环境应用突然报错 “Too many connections”,监控显示 MySQL 连接数达到上限。第一反应是调大 max_connections,但调大后过一段时间又打满了。
连接数打满往往不是因为连接池配置太小,而是有某些连接占着不释放,导致连接池耗尽。这些"僵尸连接"可能来自长时间运行的慢查询、未提交的事务、应用连接泄漏、锁等待、空闲连接超时设置不当等。
盲目扩容 max_connections 不仅解决不了根本问题,还可能导致数据库负载进一步升高,最终拖垮整个数据库。
本文从这个问题出发,梳理 MySQL 连接数问题的完整排查路径,覆盖慢查询、锁等待、未提交事务、连接泄漏、空闲连接、连接池配置、网络问题等常见场景,并给出每个场景的判断方法、修复步骤、风险控制和验证方式。
max_connections 上限max_connections 后仍然打满Sleep 状态Locked 或 Waiting for table metadata lock 状态MySQL 使用线程模型处理连接,每个连接对应一个线程(在线程池模式下例外)。
连接的生命周期:
Sleep 状态Query 或 Execute 状态Sleep 状态连接数相关参数:
max_connections:最大连接数,默认 151max_user_connections:单个用户最大连接数,默认 0(不限制)max_connect_errors:单个主机最大连接错误次数,达到后拒绝连接connect_timeout:连接握手超时时间,默认 10 秒wait_timeout:非交互式连接空闲超时时间,默认 28800 秒(8 小时)interactive_timeout:交互式连接空闲超时时间,默认 28800 秒(8 小时)
通过 SHOW PROCESSLIST 可以看到连接状态,常见状态:
Sleep:空闲状态,等待客户端发送下一个 SQLQuery:正在执行 SQLLocked:等待表锁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:等待元数据锁,可能有未提交的事务持有锁连接泄漏是指应用从连接池获取连接后,没有正确释放,导致连接一直被占用。
常见原因:
连接池是应用和数据库之间的连接管理层,可以复用连接,减少连接开销。
连接池参数:
minimumIdle:最小空闲连接数maximumPoolSize:最大连接数connectionTimeout:获取连接超时时间idleTimeout:空闲连接超时时间maxLifetime:连接最大生命周期validationTimeout:连接验证超时时间连接池配置不当会导致:
MySQL 的锁机制包括表锁、行锁、元数据锁(MDL)等。
锁等待会导致连接被阻塞,无法释放,最终导致连接数打满。
常见锁等待场景:
慢查询和长事务会长时间占用连接,导致连接无法释放。
慢查询通常是因为:
长事务通常是因为:
MySQL 连接数问题排查分为以下步骤:
登录 MySQL,查看当前连接数和最大连接数配置:
sql
SHOW 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:当前连接数 145Max_used_connections:历史最大连接数 150max_connections:最大连接数配置 151
如果 Threads_connected 接近 max_connections,说明连接数已接近上限。
如果 Max_used_connections 等于 max_connections,说明曾经打满过。
查看连接数历史趋势:
sql
SHOW 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 查看所有连接:
sql
SHOW 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:连接 IDUser:用户名Host:客户端 IP 和端口db:当前数据库Command:命令类型(Query、Sleep、Connect 等)Time:当前状态持续时间(秒)State:连接状态Info:正在执行的 SQL(部分内容,完整内容需要 SHOW FULL PROCESSLIST)
如果输出太多,可以查询 information_schema.PROCESSLIST 表:
sql
SELECT ID, USER, HOST, DB, COMMAND, TIME, STATE, LEFT(INFO, 100) AS INFO FROM information_schema.PROCESSLIST ORDER BY TIME DESC; 统计各状态的连接数:
sql
SELECT COMMAND, COUNT(*) AS count FROM information_schema.PROCESSLIST GROUP BY COMMAND ORDER BY count DESC; 示例输出:
+---------+-------+ | COMMAND | count | +---------+-------+ | Sleep | 120 | | Query | 25 | +---------+-------+
说明大部分连接处于 Sleep 状态,可能是空闲连接未释放。
统计各用户的连接数:
sql
SELECT 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 的连接数:
sql
SELECT SUBSTRING_INDEX(HOST, ':', 1) AS 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 个连接。
统计长时间运行的连接:
sql
SELECT ID, USER, HOST, DB, COMMAND, TIME, STATE, LEFT(INFO, 100) AS INFO FROM information_schema.PROCESSLIST WHERE TIME > 300 ORDER BY TIME DESC; 这会列出运行时间超过 5 分钟的连接。
查找正在执行的慢查询:
sql
SELECT 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 ... | +-----+------+----------+------+------+---------------+------------------+ 说明:
Sending dataCopying to tmp table,说明在创建临时表
对于慢查询,可以执行 EXPLAIN 分析执行计划:
sql
EXPLAIN SELECT * FROM large_table WHERE ...; 查看是否缺少索引或全表扫描。
如果慢查询正在执行且影响业务,可以 kill 掉:
sql
KILL <connection_id>; 高风险提醒:
KILL 会立即终止连接,如果是正在执行的事务,会回滚KILL QUERY 只终止 SQL,不关闭连接sql
KILL QUERY <connection_id>; 查看当前锁等待情况:
MySQL 5.7 及以上版本:
sql
SELECT 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 及以上版本,表名有变化:
sql
SELECT 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 ... | +----------------+------------------+------------------+-------------------+ 说明:
找到阻塞线程后,查看其详情:
sql
SELECT 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 掉:
sql
KILL 101; 高风险提醒:
元数据锁是 MySQL 5.5 引入的机制,用于保护表结构。
DDL 操作(ALTER TABLE、DROP TABLE)会持有排他的元数据锁,阻塞所有 DML 操作。
查看元数据锁等待(MySQL 5.7+):
sql
SELECT 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 | +---------------+--------------+-------------+-----------------------+------------+----------------+------------------------+ 说明:
users 的排他锁查看阻塞线程:
sql
SELECT * FROM information_schema.PROCESSLIST WHERE ID = 105; 如果阻塞线程是未提交的事务,可以 kill 掉。
如果阻塞线程是正在执行的 DDL,需要等待其完成或 kill 掉(有风险)。
长时间未提交的事务会持有锁,阻塞其他事务,导致连接堆积。
查看当前所有事务:
sql
SELECT trx_id, trx_state, trx_started, trx_rows_locked, trx_rows_modified, trx_mysql_thread_id, LEFT(trx_query, 100) AS 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 ... | +--------+-----------+---------------------+-----------------+-------------------+---------------------+------------------+ 说明:
trx_query 为 NULL 说明当前没有执行 SQL如果事务长时间未提交,可能是:
对于长时间未提交的事务,可以 kill 掉:
sql
KILL 101; 大量空闲连接会占用数据库连接,导致连接数打满。
查找空闲时间超过 1 小时的连接:
sql
SELECT 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 空闲连接:
sql
SELECT CONCAT('KILL ', ID, ';') AS kill_command FROM information_schema.PROCESSLIST WHERE COMMAND = 'Sleep' AND TIME > 3600 AND USER != 'root'; 这会生成 kill 命令,复制执行即可。
高风险提醒:
MySQL 的连接超时配置影响空闲连接的回收。
查看超时配置:
sql
SHOW 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 分钟)。
临时修改:
sql
SET GLOBAL wait_timeout = 600; SET GLOBAL interactive_timeout = 600;
永久修改,编辑 /etc/my.cnf:
ini
[mysqld] wait_timeout = 600 interactive_timeout = 600 重启 MySQL:
bash
systemctl restart mysql 高风险提醒:
应用连接池配置不当是连接数打满的常见原因。
常见问题:
max_connectionsmax_connections以 HikariCP(Java)为例,推荐配置:
yaml
spring: 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:最小空闲连接数 10maximum-pool-size:最大连接数 20connection-timeout:获取连接超时时间 30 秒idle-timeout:空闲连接超时时间 10 分钟max-lifetime:连接最大生命周期 30 分钟connection-test-query:连接验证 SQL连接池配置建议:
max_connections 的 80%网络问题会导致连接异常,无法正常释放。
查看连接错误:
sql
SHOW GLOBAL STATUS LIKE 'Aborted%'; 示例输出:
+------------------+-------+ | Variable_name | Value | +------------------+-------+ | Aborted_clients | 150 | | Aborted_connects | 50 | +------------------+-------+ 说明:
Aborted_clients:客户端异常断开的连接数Aborted_connects:连接握手失败的次数如果这两个值很大,说明存在网络问题或客户端异常。
常见原因:
查看系统文件描述符限制:
bash
ulimit -n cat /proc/sys/fs/file-max 如果限制太小,需要调整。
如果确认是并发量大导致连接不够,可以适当调大 max_connections。
查看当前配置:
sql
SHOW VARIABLES LIKE 'max_connections'; 临时修改:
sql
SET GLOBAL max_connections = 500;
永久修改,编辑 /etc/my.cnf:
ini
[mysqld] max_connections = 500 重启 MySQL:
bash
systemctl restart mysql 高风险提醒:
MySQL 连接数受系统文件描述符限制。
查看当前限制:
bash
ulimit -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:
bash
systemctl daemon-reload systemctl restart mysql 验证:
bash
cat /proc/$(pgrep mysqld)/limits | grep "open files" 建立连接数监控,及时发现问题。
使用 Prometheus 监控 MySQL 连接数:
安装 mysqld_exporter:
bash
wget 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 监控用户:
sql
CREATE 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 配置:
yaml
scrape_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:客户端异常断开次数告警规则:
yaml
groups: - 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 USER, COUNT(*) AS count FROM information_schema.PROCESSLIST GROUP BY USER ORDER BY count DESC; -- 按客户端 IP 统计 SELECT SUBSTRING_INDEX(HOST, ':', 1) AS 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, 100) AS 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%';
/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:
bash
systemctl restart mysql
application.yml:
yaml
spring: 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
application.yml:
yaml
spring: 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
from 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 分钟执行一次:
bash
echo "*/5 * * * * /usr/local/bin/mysql-connection-monitor.sh" | crontab -
通过 SHOW STATUS 查看连接数指标:
sql
SHOW 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 限制被拒绝的连接数查看慢查询日志配置:
sql
SHOW 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 锁等待日志:
sql
SET GLOBAL innodb_lock_wait_timeout = 50; SET GLOBAL innodb_print_all_deadlocks = ON; 查看死锁日志:
sql
SHOW ENGINE INNODB STATUSG
在 LATEST DETECTED DEADLOCK 部分可以看到最近的死锁信息。
使用脚本记录连接数趋势:
bash
#!/bin/bash while true; do 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 QUERY 只终止 SQL,不关闭连接wait_timeout 会影响所有连接清理或优化后,执行:
sql
SHOW STATUS LIKE 'Threads_connected'; 确认连接数下降。
优化慢查询后:
sql
SELECT * FROM information_schema.PROCESSLIST WHERE COMMAND = 'Query' AND TIME > 10; 确认没有长时间运行的查询。
处理锁等待后:
sql
SELECT * FROM sys.innodb_lock_waits; 确认没有锁等待。
清理空闲连接后:
sql
SELECT COUNT(*) FROM information_schema.PROCESSLIST WHERE COMMAND = 'Sleep' AND TIME > 3600; 确认长时间空闲连接已清理。
优化或清理后,验证业务功能:
如果修改配置导致问题:
bash
# 回滚配置文件 cp /etc/my.cnf.bak /etc/my.cnf # 重启 MySQL systemctl restart mysql 或者临时修改回原值:
sql
SET GLOBAL wait_timeout = 28800; SET GLOBAL max_connections = 151; 如果误 kill 连接:
建立连接数监控告警:
监控工具:
max_connections 的 80%max_connections,不要盲目扩容MySQL 连接数打满问题排查的核心思路是:
SHOW STATUS 和 SHOW PROCESSLIST 查看当前连接数和连接状态wait_timeout 和 interactive_timeoutmax_connections,但要配合监控和告警在生产环境操作时,务必注意:
max_connections 要考虑内存和负载影响
最重要的是,不要只关注扩容 max_connections,而是要找到根因,优化慢查询、锁等待、连接泄漏等问题,从源头解决问题,建立长效机制。
全部0条评论
快来发表一下你的评论吧 !