SQL Server 重启后程序连不上,回收 IIS 应用池才恢复
AI 生成声明:本文内容由 AI 生成或经 AI 辅助完成,请注意甄别。
ASP.NET Core 部署在 IIS 上,数据库用的是 Microsoft.Data.SqlClient 7.0.1 加 Dapper。SQL Server 重启完成之后,接口一直报连不上,回收应用程序池才立刻正常。
第一反应容易落到客户端版本上。这次不是。业务代码每次 new SqlConnection,用完就关,写法本身没有握着一条长连接。卡住的是进程里的连接池。
池在进程里,不在某次请求里
ADO.NET 的连接池按进程存在。IIS 里就是那个 w3wp。默认最多大约 100 条物理连接,寿命不限。逻辑上每次请求都是新的 SqlConnection,物理 TCP 却被放回池里下次复用。
数据库进程或整机重启后:
- 池里的旧 TCP 不会被主动作废。Windows 默认 TCP KeepAlive 大约 2 小时,半开连接看起来仍可用。
- 下一次
Open才会踩到这条死连接:被对端拒绝,或者一直重传到超时。池只丢掉被踩中的这一条,其余死连接继续留给后面的请求。 - 流量不高时,要失败很多次池才会排空。表现就是库已经起来了,接口还是连不上。
- 回收应用程序池会结束
w3wp,池随之销毁。新进程对已经起来的 SQL Server 建立新 TCP,所以马上正常。
/health 里执行 SELECT 1 也解决不了这件事,除非有人来打这个地址,并且失败时真的去清池。IIS 不会因为健康检查失败就自己回收。
自带重连只处理正在取出的那一条
Microsoft.Data.SqlClient 有空闲连接重连。缺省大约是重试 1 次、间隔 10 秒。它只在从池里取出某一条已经断开的连接时尝试重连,然后才把错误抛给这次请求。
它不会巡检整个池,也不会在服务器重新起来之后主动把剩下的死连接丢掉。半开连接若还没被判定为断开,这次重连甚至不会发生,请求会耗在 TCP 重传上。
所以升级客户端解决不了「必须回收应用池」这现象。7.0.1 已经是新包,缺的是池的寿命和失败后的清空。
连接串只补缺省,不覆盖已有值
在创建连接前规范化连接串。某一项如果运维已经写进连接串,就保持原值。没写才补上:
| 项 | 补上的值 | 作用 |
|---|---|---|
ConnectRetryCount |
3 | 取出的空闲连接已断开时,先重连 |
ConnectRetryInterval |
2 秒 | 缩短缺省的 10 秒间隔 |
Load Balance Timeout |
60 秒 | 连接归还池时,超过 60 秒就销毁 |
Connect Timeout |
15 秒 | 建连等待 |
Min Pool Size |
0 | 不预留永远占着的物理连接 |
Pooling |
true | 继续使用连接池 |
Load Balance Timeout 就是连接寿命(Connection Lifetime)。设成 0 时,一条物理连接可以活过整个应用池寿命。60 秒只是让归还到池里的旧连接尽快淘汰,不是关掉池。
不要把 Pooling 设成 false。那样每次请求都新建 TCP,数据库重启后确实不会留死连接,但建连压力会全部打到 SQL Server 上。
探活失败时清空整个池
重试和寿命仍然是「用到某条连接时才处理它」。数据库已经恢复、池里还堆着死连接时,低流量接口会一条一条失败。需要有人把池整个丢掉。
加一个后台任务,大约每 20 秒用同一套连接工厂执行 SELECT 1,命令超时 3 秒:
- 失败:若距上次清空已超过约 10 秒,调用
SqlConnection.ClearAllPools(),记一条「数据库不可达,已清空连接池」。 - 由失败变为成功:记一条「数据库已恢复」。成功本身不再清池,因为不可达期间已经清过。
- 只处理这次探活的失败。业务 SQL 的死锁、唯一键冲突、慢查询超时不去清池。
/health 失败时走同一个清空方法。外面如果有监控来打健康检查,也能顺便把池清掉。10 秒防抖是为了探活和健康检查同时失败时不要连着清。
库还没起来时,清空是空操作。库起来之后,池里已经没有旧 TCP,下一个业务请求会新建连接。最坏大约一个探活周期,20 秒内恢复,不用再等人工回收应用池。
示意如下:
public static bool TryClearPools()
{
var now = Environment.TickCount64;
var last = Interlocked.Read(ref _lastClearTicks);
if (last != long.MinValue && unchecked(now - last) < 10_000)
return false;
if (Interlocked.CompareExchange(ref _lastClearTicks, now, last) != last)
return false;
SqlConnection.ClearAllPools();
return true;
}
ClearAllPools() 清的是当前进程里全部 SQL Server 连接池。如果同一个进程还连着别的 SQL Server,探活失败时会一起清掉。这次只有一个业务库,可以接受。MySQL 用的是另一套客户端,不受这次清空影响。
发布之后才对正在跑的进程生效
这些逻辑在新进程里才会执行。已经在 IIS 里跑着的 w3wp 仍是旧程序,连接池也还是旧的。要把新的 API 发布出去,让应用池加载新程序,之后的数据库重启才不必再手工回收。
如果本文吸引了你,为你带来了灵感,帮助,麻烦你轻轻点击【推荐】,
你的热心评论,推荐,是我分享文章的强大动力之一。谢谢阅读!
作者:CatcherX
主页:http://catcherX.cnblogs.com
关于我:热爱IT技术、擅于用技术解决实际应用问题,目前专攻Web,网络应用开发。
.Net平台下的ASP.NET MVC;
WinForm(CS端管理系统、数据采集分析、网络应用)开发工作;
熟悉数据库SqlServer开发,Oracle管理技术;
前端Html,Css,JavaScript,Ajax;
涉猎网络安全,运维,电工基础;闲谈文学艺术。
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。
或邮件:CatcherXue(at)hotmail(dot)com
联系我,非常感谢。

浙公网安备 33010602011771号