你的网站又卡了?八成是数据库连接数撑不住了
页面转圈、接口报错、后台打不开……查日志一看全是 Too many connections。别急着重启服务,这问题其实挺常见,也比你想的更容易摸清门道。
第一步:别急着加连接数,先搞清楚谁在“占着茅坑”
看到报错第一反应调大 max_connections?先停一停。这就像会议室满了,你不去看看谁坐在里面发呆,反而想着再搭个帐篷——问题不在空间,而在人怎么坐。
马上连进数据库,跑这条命令:
SHOW PROCESSLIST;
重点盯三类连接:
State是Sleep,但Time超过 60 秒的(空着不走)Command是Query,Time却一直涨的(卡在执行)Info里明显是重复请求、没参数、或者带SELECT * FROM xxx WHERE id = ?却没走索引的(大概率慢查询)
真实案例:一个做本地生活的后台系统,高峰期总崩。一查 PROCESSLIST,发现 80% 的连接都是 Sleep 状态,最长的挂了 4 小时。最后定位到是某段老代码用完连接后没调 close(),还配了个超长的 idleTimeout=3600000,等于主动把连接锁死一小时。
连接池配置:你的连接管理“大管家”用对了吗?
连接池不是越大越好,它更像一个值班表——排得太满,人累;排得太松,活干不完。出问题,9 成都在这里。
打开你的应用配置文件(比如 application.yml 或 druid.properties),检查这几项:
maxActive/maxTotal:别设成数据库max_connections的 80%,建议控制在 30%~50%minIdle:别为了“预热”设太高,2~5 就够,启动时占一堆空连接很伤maxWaitMillis:别设成-1(无限等),3000 毫秒是合理底线timeBetweenEvictionRunsMillis+minEvictableIdleTimeMillis:必须配!建议前者 30000(30 秒扫一次),后者 60000(空闲超 1 分钟就回收)
特别提醒:Spring Boot 默认 HikariCP 的 connection-timeout 是 30 秒,但很多团队没改 idle-timeout(默认 10 分钟),这就埋下了“僵尸连接”的种子。
SQL语句是性能黑洞?慢查询拖垮整个连接池
一条慢 SQL,就是一个卡住的线程。它不释放连接,后面所有请求全得排队。这不是性能问题,是资源死锁。
立刻确认数据库是否开了慢查询日志:
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 2;
然后去翻日志,找 Query_time 超过 2 秒的语句。重点关注:
WHERE条件字段有没有索引?ORDER BY字段是不是在联合索引最左?JOIN表有没有加驱动表提示?- 是不是写了
SELECT *,却只用其中两三个字段?
真实案例:一个 SaaS 后台的客户列表页,每次打开都卡顿。慢日志里反复出现一条 SELECT * FROM customers WHERE status = ? ORDER BY created_at DESC LIMIT 20。表有千万级数据,但 status 和 created_at 没联合索引。补上 (status, created_at) 索引后,查询从秒级降到毫秒级,该页面的连接占用时间明显下降,用户不再反馈“点不动”。
长事务和失效的索引:连接被长期“绑架”的元凶
事务不是越长越好。一个事务里做了三次 HTTP 调用、等用户填表单、再更新数据库?那这个连接可能被锁住几分钟——别人想读同一行数据,只能干等。
原则就一条:事务只包数据库操作,其他一律搬出去。HTTP 请求、文件写入、消息发送,全都放到 @Transactional 外面。
另外,索引不是建完就一劳永逸。数据量翻倍、分布变化、字符集升级……都可能让优化器“看走眼”。定期用 EXPLAIN 看关键查询:
EXPLAIN SELECT * FROM orders WHERE user_id = 123 AND status = 'paid';
如果 type 是 ALL,或者 key 显示 <null>,说明索引没用上。这时候别急着重建,先试试:
ANALYZE TABLE orders;
统计信息一更新,有时候优化器自己就“醒”了。
架构层面:读写分离和缓存能救你的命吗?
当单库连接数稳定在 max_connections 的 70% 以上,且优化完 SQL 和连接池仍没缓解,就得动架构了。
读写分离不是高大上概念,很多项目已经用上了:
- MyBatis Plus 支持
@DS("slave")切数据源 - ShardingSphere-JDBC 只要改个配置就能自动路由读请求到从库
- Spring Cloud Alibaba Seata 也能配合做读写分离兜底
关键是:别让所有读都压主库。用户中心、商品详情、订单历史这些读多写少的接口,优先切到从库。
缓存不是万能胶,但它是最快见效的减压阀。
- 用户登录态、权限菜单、系统配置 → 丢进 Redis,TTL 设 30 分钟起
- 商品基础信息、分类树、热门搜索词 → 加个本地缓存(Caffeine)+ Redis 双层,避免缓存击穿
- 页面片段(比如首页推荐位、活动 banner)→ 渲染好直接缓存 HTML 片段,过期时间按业务节奏设(比如 5 分钟)
真实案例:一个教育平台的课程详情页,每次访问要查 6 张表拼数据。团队把课程基础信息 + 教师简介 + 章节大纲三块拆出来,分别缓存到 Redis,过期时间设为 10 分钟。上线后,该接口的数据库连接数消耗降到了原来的零头,页面加载速度也翻了好几倍。
除了重启,今天下班前你能做什么?
别等明天,现在打开终端/Navicat/MySQL Workbench,照着做:
查现状:运行
SHOW VARIABLES LIKE 'max_connections'; SHOW STATUS LIKE 'Threads_connected';记下两个数字,心里就有谱了。
揪异常:运行
SHOW PROCESSLIST;把结果按
Time倒序,截图发给开发同事,标出所有Time > 60且State = 'Sleep'的连接,以及State是Sending data或Sorting result的活跃查询。翻配置:打开你项目的
application.yml(或druid.properties),找到连接池配置段,确认:max-active/max-total没超过数据库max_connections的一半min-evictable-idle-time-millis或idle-timeout已设置(建议 60000)connection-timeout不是 -1,而是 3000 左右
推一把:把第 2 步的截图 + 第 3 步的配置问题,直接钉钉/飞书发给后端负责人,标题写清楚:“【紧急】数据库连接堆积,疑似空闲连接未回收 + 某些查询卡住,请帮忙看下对应代码逻辑”。
临时止血(仅限低峰):如果连接数已飙到 95% 以上,且确认某些
Sleep连接确实无业务关联(比如来自测试脚本、旧监控任务),可以手动KILL [id]——但一定要先SELECT ID, USER, HOST, DB, COMMAND, TIME, STATE, INFO FROM information_schema.PROCESSLIST;再核对,别误杀。
做完这五步,你至少知道“谁占着、为什么占、怎么赶”,而不是靠重启续命。接下来,才是深入查慢 SQL、调事务、上缓存的节奏。