数据库连接与超时:等待、重试与熔断
青梅煮雪
4 min read
判断先说:数据库连接是有限且昂贵的资源。多数"数据库突然变慢"的事故,根因不在 SQL,而在连接被耗尽、超时设置不合理、重试风暴三件事叠加。把连接池、超时、重试、熔断四个旋钮调好,系统稳定性会直接上一个台阶,比优化单条 SQL 更影响全局。
连接池不是越大越好
每个连接占用内存,数据库也要为每个连接维护会话状态、事务上下文。连接数超过数据库承受能力后,上下文切换和锁竞争反而让吞吐下降,出现"连接越多越慢"的怪象。很多团队默认把池子设成几百,结果把数据库压垮。
# 经验上连接池上限参考(OLTP 粗略估算)
最大连接数 ≈ (核心数 * 2) + 有效磁盘数
更实际的做法是按业务峰值并发反推:池子要能覆盖正常并发,又给数据库留余量。盲目设成上千,往往是在给数据库挖坑。还要设最小空闲连接、最大空闲时长,避免空闲连接被中间件或防火墙悄悄掐断后变成"僵尸连接"——应用以为连着,实际早已断掉,拿到就报错。
超时必须分层设
只设一个总超时往往不够,至少要分清三类,缺了任何一类都会出事:
获取连接超时 → 从池里拿连接最多等多久(拿不到说明池满)
执行超时 → 单条 SQL 最长跑多久(慢 SQL 早失败早释放)
网络/套接字超时 → 与数据库网络交互的底层超时
缺了"获取连接超时",池满时线程会无限等待,最终拖垮整个应用线程池;缺了"执行超时",一条慢查询占着连接不放,池子很快被占满并连锁阻塞。经验上,执行超时设为业务能接受的最长响应,宁可失败也不长期占用连接。一个常见错误是把超时设得极大甚至不设,结果一个慢查询把整池连接耗尽,所有请求全部排队。
重试要带退避和上限
网络抖动下,盲目立即重试会放大流量,把本来能恢复的小故障变成大故障。正确做法是有限次重试加指数退避加抖动:
第1次失败 → 等 100ms 重试
第2次失败 → 等 200ms 重试
第3次失败 → 等 400ms 重试 → 仍失败则放弃
两个陷阱必须避开。一是重试必须针对"可重试错误"(超时、连接断开),对"数据冲突、约束违反"这类确定性错误重试毫无意义,只会重复报错。二是分布式下的非幂等写操作,重试可能造成重复提交,必须在业务层保证幂等(唯一键、去重表、幂等 token)后再重试,否则一次网络超时重发就可能扣两次款。
熔断挡住持续恶化
当数据库已经不堪重负,继续发请求只会让它更慢、恢复更慢,陷入死亡螺旋。熔断器的思路是:错误率或慢调用超过阈值,就先"开路"拒绝一段时间,让后端喘息,再进入半开状态试探恢复。
正常 → 错误率超阈值 → 熔断(快速失败)
冷却期后 → 半开(放少量请求试探)
试探成功 → 恢复关闭; 失败 → 继续熔断
熔断和连接池上限、超时是互补的:连接池上限防"瞬时占满",超时防"单请求长期占用",熔断防"后端已病、流量还往里灌"。三者一起,数据库即便出问题,影响也被限制在一定范围内,而不是全链路雪崩。实践中,把熔断阈值设得稍微灵敏些,比等连接全满再反应更主动。
一个整体视角
连接池、超时、重试、熔断四者要配合看,不能孤立调。一个典型事故链:超时设太长 → 慢 SQL 占满连接池 → 新请求拿不到连接无限等 → 上层不断重试放大流量 → 数据库彻底崩溃。只要在其中一环(比如收紧执行超时、加熔断)切断,链就断不了。定期用压测验证这套组合在极限下的表现,比上线后靠运气重要得多。
1 view
0 likes
0 comments