ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

MySQL连接数从查看到配置的完整指南:定位瓶颈、计算上限与排查故障

MySQL连接数从查看到配置的完整指南:定位瓶颈、计算上限与排查故障 连接数这东西平时没人注意出问题就是大事。我见过太多项目跑得好好的某天流量稍微上来一点应用端瞬间报出一片Too many connections运维同学第一反应就是把max_connections调大结果调大了还是报错最后发现是连接根本没释放把数据库彻底拖垮。这个坑我踩过不止一次所以今天把 MySQL 连接数从查询到配置的完整套路整理一遍包括怎么查看当前连接、怎么定位是谁占满了连接、怎么根据机器配置算出合理的上限以及常见故障的排查思路。适合刚接触 MySQL 的开发者也给正在被连接数问题折磨的运维同学做个参考。1. 先弄清楚连接数到底是什么以及为什么它会变成瓶颈1.1 一条连接在 MySQL 里的生命周期很多人把“数据库连接”理解成“登录一次”其实没这么简单。在 MySQL 里一条连接从建立到释放经历的是完整的网络连接 鉴权 会话初始化流程。应用端的连接池发起 TCP 握手MySQL 端完成用户校验、权限检查然后分配一个线程来处理这条连接上的所有查询请求。也就是说MySQL 内部每多一条连接就要多占用一个线程同时还要为这个会话分配排序缓冲区、join 缓冲区、临时表空间等内存资源。我经常拿餐厅来打比方MySQL 就像一家后厨连接数是厨房里能同时开工的灶台数量。灶台再多也有限如果每个进来的客人都占着一个灶台不动后厨再大也会崩溃。应用端的连接池、慢查询、长事务都可能让灶台被占着不释放。1.2 连接数太少和太多分别会出什么问题连接数太少症状非常直接应用报Too many connections连接直接被拒绝。这个报错不是你 SQL 写得有问题而是 MySQL 连准入都不准了。更麻烦的是这个错误通常不只在出问题的那台应用上出现而是所有应用实例同时遭殃因为你把全局连接数打满了。连接数太多情况更隐蔽。MySQL 不是无限资源每个连接都要吃内存和 CPU。当连接数冲到几百上千即使还没到max_connections上限也会出现整体性能下降查询变慢、锁等待变多、CPU 飙升。很多新手以为“连接数没到上限就没问题”这是大错特错。真正的临界点往往比配置上限低得多。1.3 为什么静态配置无法解决动态问题还有一个认知要纠正连接数配置不是“一劳永逸”的事。MySQL 的max_connections只是一个静态上限它决定了 MySQL 最多愿意同时接待多少条连接。但你这个应用到底需要多少连接取决于应用层连接池配置、并发请求量、每个请求执行时间、是否有慢查询、是否有长事务。今天这个配置可能是合理的明天上线一个慢接口半小时内就能把连接数吃满。所以连接数的处理一定是“查询—分析—配置—验证—再观察”的循环而不是改一个参数就完事。这也是我写这篇文章的初衷把查询和配置的完整方法论讲清楚让大家遇到问题能自己定位和解决。2. 查询当前连接数的实用命令与状态指标解读2.1 一条 SQL 查看当前连接数最常用的查询命令是这样SHOW STATUS LIKE Threads_connected;这个Threads_connected就是“当前有多少活跃连接”。注意这里统计的是当前连接到 MySQL 的客户端连接数包括空闲连接。只要应用连接池还保持着连接不释放即使没有查询在跑它也会被算进去。如果想看更直观的信息可以用SHOW PROCESSLIST;这个命令会列出所有 Sherthik 在执行或者其他状态的连接。它还会显示每个连接在干什么比如Sleep表示空闲Query表示正在执行查询Locked表示在等待锁。这是排查连接数问题时的第一手资料后面排查部分我会细说。2.2 查看连接数相关的其他状态变量光看Threads_connected还不够我一般会把下面这几个状态变量一起拉出来看SHOW GLOBAL STATUS LIKE Threads%; SHOW GLOBAL STATUS LIKE Max_used_connections; SHOW GLOBAL STATUS LIKE Connections; SHOW GLOBAL STATUS LIKE Aborted_connects;Threads_connected当前连接的线程数。Threads_running当前正在执行查询的线程数。这个值很关键如果它长期大于 CPU 核数说明数据库压力已经很大了。Max_used_connections自上次重启以来同时存在的最大连接数。这个值直接告诉你历史峰值有没有碰过天花板。Connections累计尝试连接 MySQL 的总次数包括成功的和失败的。Aborted_connects连接尝试失败的次数。如果这个值持续增长就要注意是不是认证失败或者连接被中断。这些指标配合起来看才能判断当前连接数是“正常水平”还是“危险水平”。2.3 查看最大连接数配置最大连接数就是max_connections可以通过下面命令查看SHOW VARIABLES LIKE max_connections;默认值通常是151。对很多小网站来说够用但对稍微有点并发量的应用来说这个默认值往往是不够的。我还见过一些人直接用SET GLOBAL max_connections 2000;改完就走没有写进配置文件结果 MySQL 一重启又变回 151这才是真正的大坑。2.4 组合查询一条 SQL 搞定所有关键指标我自己在实际排查时不会分开敲好几条命令而是把它们拼在一起看SELECT max_connections AS max_conn, (SELECT COUNT(*) FROM information_schema.processlist) AS current_conn, (SELECT MAX(cnt) FROM ( SELECT COUNT(*) AS cnt FROM information_schema.processlist GROUP BY db ) t) AS max_per_db, (SELECT COUNT(*) FROM information_schema.processlist WHERE command Sleep) AS sleep_conn;不过information_schema.processlist在连接数很多的时候查询比较慢线上大并发环境不太建议频繁执行。替代方案是直接看performance_schema里的连接状态后面我会单独说。2.5 用 performance_schema 定位每个用户的连接占用当你发现连接数满了最想知道的就是“谁占的”。用SHOW PROCESSLIST能看但信息不够结构化。更好的方式是查performance_schemaSELECT user, host, db, command, COUNT(*) AS cnt FROM performance_schema.threads WHERE processlist_id IS NOT NULL GROUP BY user, host, db, command ORDER BY cnt DESC;这个查询能按用户、来源主机、当前数据库和执行状态分类统计连接数一秒定位是哪个应用、哪台机器在疯狂建立连接。比如看到某个user对应commandSleep的几条连接占了大几百那大概率就是这台机器的连接池配置出了问题。3. 连接数上限的合理配置从参数到计算公式3.1 修改 max_connections 的正确姿势临时修改不需要重启数据库在线就能生效SET GLOBAL max_connections 500;但注意这是用GLOBAL级别设置的只对后续新连接生效已经存在的连接不受影响。而且一旦 MySQL 重启这个值就会丢失。要永久修改必须改配置文件。在 Linux 环境下一般是编辑/etc/my.cnf或/etc/mysql/mysql.conf.d/mysqld.cnf在[mysqld]段下加一行[mysqld] max_connections 500改完配置文件后重启 MySQL 服务sudo systemctl restart mysqld重启后再次确认SHOW VARIABLES LIKE max_connections;3.2 其他连接数相关参数不要只盯着 max_connections光调max_connections是不够的连接数问题还和下面几个参数相关max_user_connections限制单个 MySQL 用户的最大连接数。防止某个应用把全局连接数打满非常实用。max_connect_errors一台主机连续连接失败达到这个次数后MySQL 会暂时禁止它连接。这个值默认比较小如果应用配置错误反复重连很容易触发。wait_timeout和interactive_timeout非交互连接和交互连接的空闲超时时间默认 8 小时。如果应用连接池不主动释放空闲连接会占用大量连接数。skip_name_resolve关闭 MySQL 对客户端的反向 DNS 解析。开启后可以降低连接建立时的延迟也能避免 DNS 解析超时导致的连接问题。我见过不少案例max_connections已经调到 1000但连接数还是很快见顶最后发现是wait_timeout太长一堆Sleep连接占着位置不释放。把wait_timeout调到 60 秒之后连接数明显降下来了。3.3 连接数到底设置多少合适怎么算这是很多人的困惑。max_connections设太小容易爆设太大则浪费内存。一个简单的估算方法是用 MySQL 平均单连接占用内存乘以预估连接数再和可用内存对比。首先查看单连接平均内存消耗可以通过performance_schema或经验值来估算。简单粗暴的办法是看 MySQL 进程占用内存和当前连接数的比值ps aux | grep mysqld假设 MySQL 进程占用了 30GB 内存当前有 300 条连接那平均每条连接大概消耗 100MB 左右。如果你的服务器分配给 MySQL 的内存是 48GB预留 20% 给系统和其他进程那么安全连接数大概是可用内存 48 * 0.8 38.4GB 单连接内存 0.1GB 理论最大连接数 38.4 / 0.1 384当然这不是精确值因为不同查询消耗的内存差别很大。排序、临时表、大查询会让单连接内存飙升几倍甚至几十倍。所以实际设置时建议留足安全余量。比如理论算下来 384线上可以先设 300观察Max_used_connections会不会接近上限再慢慢调整。不要一味调大我见过有人把max_connections设成 10000结果 MySQL 启动后直接 OOM就因为没算内存账。3.4 如何设置 max_user_connections 限制单个应用如果线上有多个业务共用一个 MySQL 实例强烈建议给每个业务设置独立的账号并限制单用户最大连接数。比如某个核心应用最多只给 200 个连接CREATE USER app_ad% IDENTIFIED BY xxxx; GRANT SELECT, INSERT, UPDATE, DELETE ON appdb.* TO app_ad%; SET GLOBAL max_user_connections 0;这里max_user_connections 0表示全局不限制。然后对指定用户单独限制ALTER USER app_ad% WITH MAX_USER_CONNECTIONS 200;这样即使这个应用的连接池配置失误把连接数冲到了 500它最多也只能建立 200 条连接不会拖垮整个数据库实例。这个做法在多人共用的数据库上特别有用。4. 应用层连接池与 MySQL 连接数的联动配置4.1 连接池才是连接数管理的真正主战场很多人一遇到连接数问题就改 MySQL 配置其实大部分时候问题出在应用层的连接池。Java 系的 HikariCP、DruidGo 的 database/sql 连接池Python 的 SQLAlchemy 连接池每个都有自己的并发模型。连接池的本质是复用而不是无限创建。它内部维护一组数据库连接请求来了从池里拿用完还回去避免每次请求都新建连接。新建连接的成本非常高TCP 握手加 MySQL 鉴权一次可能就要几十毫秒在高并发下会浪费大量时间和资源。但连接池有个很容易踩的坑最大连接数配的过大但 MySQL 端max_connections没同步调整。比如连接池最大连接数配了 500线上有 10 个应用实例每个应用自己感觉都“没问题”但 MySQL 端要承受 5000 条连接的洪峰直接打满。4.2 连接池大小怎么设置标准参考值业内有一个比较经典的经验公式连接数 ((核心数 * 2) 有效磁盘数)这来自 PostgreSQL 圈的知名建议MySQL 也适用。对于一个 4 核的数据库服务器连接数初期可以设置为((4 * 2) 1) 9左右。当然这个值看上去很小很多人不敢信但它背后的逻辑是如果一个连接足够快根本不需要太多连接如果查询慢连接再多也只会加剧竞争和上下文切换。我通常建议从 20 到 50 起步观察数据库的 CPU 和响应时间再调。如果查询基本都是几十毫秒内完成几十个连接完全够用如果频繁出现慢查询连接数再多也解决不了问题反而会把问题放大。4.3 连接池的超时与保活参数连接池里除了最大连接数还有几个参数必须关注最小空闲连接数保证任何时候都有一些现成连接可用减少冷启动等待。连接最大空闲时间空闲超过这个时间就回收防止 MySQL 端wait_timeout掐断连接。连接超时时间请求从池里拿不到连接时等待多久后抛异常。这个时间设太短容易误报设太长则请求堆积。连接存活检查通过一个轻量的探活 SQL 定期检测连接是否有效。拿 Java 的 HikariCP 举例一个比较稳的配置是spring: datasource: hikari: minimum-idle: 10 maximum-pool-size: 50 connection-timeout: 30000 idle-timeout: 600000 max-lifetime: 1800000 connection-test-query: SELECT 1这里maximum-pool-size: 50表示这个应用实例最多占用 50 条 MySQL 连接。如果部署 5 个实例MySQL 端最大需要支持50 * 5 250条连接。再加上监控、备份等其他连接MySQL 的max_connections就可以设定在 300 左右。5. 常见连接数问题与排查技巧实录5.1 一觉醒来发现 Too many connections这是最经典的问题某天早上应用日志里突然全是Too many connections。第一步不要慌先尝试用管理员账号连上 MySQL注意这时可能普通连接进不去但 root 账号有时也进不去。如果 root 也进不去就需要重启 MySQL 或者用gdb等高级手段但那样会丢所有连接。更稳妥的顺序是先看监控如果连不上就用mysqladmin带扩展参数尝试。实际上只要 MySQL 还没到完全无响应用 root 连接通常也是可以的。连接上之后立刻执行SHOW PROCESSLIST;重点看Command列大量Sleep意味着连接池没有正确释放连接大量Query且Time很高说明有慢查询堆积大量Connect说明应用在频繁创建新连接。5.2 排查连接泄漏谁能告诉我连接去哪了连接泄漏是最头疼的问题。应用的连接池借出连接之后代码里没有 finally 归还时间一长池里的连接就全被借光新请求全部阻塞或报错。定位方法分为两步第一步在数据库端找出哪些连接长时间空闲却始终不释放SELECT id, user, host, db, command, time, state, info FROM information_schema.processlist WHERE command Sleep AND time 60;如果大量连接处于Sleep超过 60 秒基本可以判断是应用层连接泄漏或缺少回收机制。第二步去应用日志查连接池借出和归还的记录。HikariCP 的日志里会有借出连接未归还的警告Druid 则可以通过监控面板看到活跃连接数和闲置连接数。还有一种更隐蔽的情况代码里开了事务但事务没有及时提交或回滚导致连接一直处于活跃状态。这种问题的排查重点不是连接池而是业务代码里的事务边界。5.3 修改配置后不生效重启就还原这是很多人忽略的SET GLOBAL和配置文件的关系。SET GLOBAL只是临时修改MySQL 重启后会用配置文件里的值。如果你修改了/etc/my.cnf但重启后不生效大概率是 my.cnf 的[mysqld]段写错了位置或者没有加载到你改的那个配置文件。先用这个命令确认 MySQL 读取了哪些配置文件mysqld --verbose --help | grep -A 1 Default options它会列出 MySQL 启动时会读取的配置文件路径按顺序加载后面的配置会覆盖前面的。如果你把max_connections写在/etc/my.cnf的[client]段下面MySQL 服务端是不会读取的。5.4 连接数配置合理却仍然性能差有时候连接数没碰顶但数据库还是很慢。这时要看Threads_running和 CPU 核数的关系。SHOW GLOBAL STATUS LIKE Threads_running;如果Threads_running长期大于 CPU 核数说明有大量查询在同时争抢 CPU。这种情况下单纯调连接数没有用应该优先处理慢查询给业务加缓存或者考虑读写分离。还有一种容易被忽略的情况max_connections调的很大但open_files_limit没同步调整。每条连接至少需要一个文件描述符如果操作系统的文件描述符限制太小连接数一高就会抛Cant create a new thread或者Too many open files。这时候即使调大max_connections也白搭需要在操作系统层面同步调大 ulimit。5.5 常见问题速查表现象可能原因处理方向报 Too many connections连接数打满或连接池配的过大查 processlist释放空闲连接调大 max_connections限制单用户连接数大量 Sleep 连接连接池未回收或超时时间过长调小 wait_timeout检查连接池最小空闲和最大生命周期连接建立很慢DNS 反向解析超时开启 skip_name_resolve连接数一高 CPU 就飙升Threads_running 过高慢查询堆积优化慢查询加缓存考虑读写分离MySQL 重启后配置失效配置文件未修改或路径不对确认配置文件加载路径把配置写到 [mysqld] 段报 Cant create a new thread操作系统线程或文件描述符不足调大 ulimit 和 open_files_limit5.6 一个完整的现场排查示例最后分享一个我前几天刚处理的案例。一个业务系统的连接数在下午突然冲到 400max_connections是 500虽然没有爆但接口响应已经从 50ms 涨到了 800ms。我先执行了这条组合查询SELECT user, host, db, command, count(*) AS cnt FROM performance_schema.threads WHERE processlist_id IS NOT NULL GROUP BY user, host, db, command ORDER BY cnt DESC;结果发现一台应用服务器的连接数占了 300其中 260 条是Sleep状态。再看这台应用服务器的连接池配置maximum-pool-size配的是 100但部署了 3 个实例加起来确实是 300。问题在于 MySQL 端wait_timeout是默认的 8 小时连接池里的空闲连接一直挂着不释放而且应用侧又开启了cachePrepStmts每个连接上还缓存了不少预处理语句内存开销不小。处理方式把连接池的maximum-pool-size从 100 降到 50idle-timeout设成 10 分钟max-lifetime设成 30 分钟同时把 MySQL 的wait_timeout调成 120 秒。改完之后连接数稳定在 80 上下接口响应也恢复到正常水平。这个例子说明连接数问题绝大多数不是单靠调大max_connections能解决的而是应用层和数据库层协同配置的结果。6. 一些值得记住的实操经验再补充几个我在实际运维中总结的小经验。如果是云数据库比如 RDS 系列连接数参数通常有特殊的修改入口而且还有“连接数上限”之外的隐藏限制比如 max_total_connections 和 max_user_connections 的组合。修改前先看一下控制台上的参数组说明不要直接在实例内执行SET GLOBAL因为云厂商的配置管理机制不一定允许你这么改重启后可能还会被覆盖。在排查连接数问题时优先查看 MySQL 的错误日志。很多连接数相关的线索比如Aborted connection、Connection closed等都会记录在错误日志里。默认位置一般是/var/log/mysql/error.log也可能是/var/lib/mysql/*.err。不要只盯着应用日志。还有一点备份脚本、监控系统、定时任务也会占用连接。我之前排查过一个案例每天凌晨 3 点数据库连接数飙升最后发现是 cron 脚本里配置的 mysqldump 每次都是新建连接而且备份操作长时间运行把连接数顶了上去。后来给备份任务单独分配了一个账号并用max_user_connections10限制该账号连接数上限问题就解决了。最后再说一个非常容易被忽略的点连接数监控一定要做历史趋势而不是只看当前值。Max_used_connections这个值虽然历史累加但能反映自重启以来的峰值建议结合监控系统把线程数、Running 线程数、活跃连接数的曲线都记录下来。当某天连接数开始异常增长时有历史数据可以参考能更快判断是业务自然增长还是代码 bug。连接数这东西说难不难说简单也不简单。核心思路就是查当前值、算上限、找占用、调配置、观察趋势。把这几步做好连接数问题基本不会再来困扰你。
返回列表