C# MMO应用中SqlCommand超时及SQL连接泄漏问题求助
解决异步TCP Socket项目中SQL连接泄漏与超时问题
这问题我之前帮好几个开发者排查过,核心大概率是连接没有被正确释放回连接池,加上高负载下连接池耗尽,最终触发SqlCommand超时。咱们一步步拆解问题,找到根因和解决方案:
核心原因梳理
SQL Server默认开启连接池,正常调用SqlConnection.Close()或用using块释放连接时,连接会被放回池里复用,而非真正关闭物理连接。但你的连接计数持续增长,说明连接没被正确回收,导致连接池被占满——新请求拿不到可用连接,排队超过30秒就触发了超时。
常见排查方向&修复方案
1. 异步代码中using与await的误用
这是最常见的坑!异步场景下,如果没正确等待数据库操作完成,或者using块没包裹整个异步流程,会导致连接提前被释放或无法回收。
错误示例:
// 问题:没await异步操作,using块结束后连接被关闭,但存储过程还在执行 using(var conn = new SqlConnection(connectionString)) { conn.Open(); var cmd = new SqlCommand("MyStoredProc", conn); cmd.ExecuteNonQueryAsync(); // 缺少await,连接提前被回收 }
正确写法:
using(var conn = new SqlConnection(connectionString)) { await conn.OpenAsync(); // 异步打开连接 using(var cmd = new SqlCommand("MyStoredProc", conn)) { cmd.CommandType = CommandType.StoredProcedure; // 添加存储过程参数... await cmd.ExecuteNonQueryAsync(); // 必须等待异步操作完成 } }
确保所有数据库相关的异步方法都加了await,且using块完整包裹从连接打开到命令执行结束的全流程。
2. 未处理的异常导致连接泄漏
如果数据库操作过程中抛出未捕获的异常,会不会影响连接回收?其实using块会自动调用Dispose(),把连接放回池,但如果是async void方法里的异常(比如TCP请求处理用了async void),异常会直接抛到线程池,可能导致后续流程异常,间接引发连接泄漏。
修复方案:
- 避免在业务逻辑中使用
async void,改用async Task; - 给数据库操作添加
try-catch,记录异常并确保流程正常收尾:
try { using(var conn = new SqlConnection(connectionString)) { await conn.OpenAsync(); using(var cmd = new SqlCommand("MyStoredProc", conn)) { // 参数配置与执行逻辑 await cmd.ExecuteReaderAsync(); } } } catch(Exception ex) { // 详细记录异常信息(包括请求ID、时间、存储过程名) Logger.Error($"请求[{requestId}]执行存储过程失败: {ex.Message}", ex); throw; // 重新抛出,让上层处理请求错误 }
3. 连接池配置或存储过程性能问题
- 连接池上限不足:默认连接池最大连接数在.NET Framework是100,.NET Core+是100/核心。如果高负载请求数超过这个值,新请求会排队等待连接,超时触发。可以临时增大连接字符串的
Max Pool Size(比如设为200),但这只是缓解,核心还是要解决连接泄漏:Server=YourServer;Database=YourDB;User Id=YourUser;Password=YourPwd;Max Pool Size=200; - 存储过程执行过慢:如果存储过程本身执行时间长,会导致连接被长时间占用,连接池可用连接越来越少。可以用SQL Server的
sys.dm_exec_requests或Profiler跟踪存储过程的执行时间,排查是否有锁阻塞、慢查询等问题。
排查验证步骤
- 监控连接池状态:用Windows性能计数器查看
SqlClient Connection Pool\Number of Active Connections,或者SQL Server的General Statistics\User Connections,确认活跃连接数是否持续增长; - 测试连接回收:在测试环境中调用
SqlConnection.ClearAllPools(),如果调用后连接计数骤降,说明确实是连接没被放回池; - 检查长连接会话:用
sp_who2或sys.dm_exec_sessions查看当前数据库会话,有没有长时间处于running或sleeping状态的会话,对应到你的应用进程。
内容的提问来源于stack exchange,提问作者Reza Akraminejad
相关产品推荐
相关产品推荐

