执行SQL脚本后内存居高不下,list=null/clear()无效的解决办法
内存持续增长问题解决方案
问题根源分析
- 字符串拼接引发大量内存碎片:
GetSqlList中使用string类型的commandText进行拼接,C#里string是不可变类型,每次拼接都会生成新的字符串对象。400KB的SQL文件会产生大量临时字符串,这些对象不仅占用大量内存,还会加重GC回收压力。 - StreamReader资源未可靠释放:虽然调用了
reader.Close(),但未使用using语句,无法保证异常场景下资源也能及时释放,可能导致句柄和内存泄漏。 - List
扩容残留内存 :默认创建的List<string>初始容量较小,添加元素时会频繁触发扩容,旧的数组对象可能暂时无法被GC回收。
具体修复步骤
1. 替换字符串拼接为StringBuilder
用StringBuilder替代string进行SQL语句拼接,避免频繁创建新字符串对象:
private static List<string> GetSqlList(string sqlPath, string database) { var sqllist = new List<string>(); try { // 预设足够初始容量,减少扩容次数 var commandText = new StringBuilder(1024 * 1024); string varLine = ""; // 使用using自动释放StreamReader资源 using (var reader = new StreamReader(sqlPath, Encoding.GetEncoding("gb2312"))) { while (reader.Peek() > -1) { varLine = reader.ReadLine(); varLine = varLine.Replace("[kms]", "[" + database + "]"); if (string.IsNullOrEmpty(varLine)) { continue; } var trimmedLine = varLine.Trim(); if (trimmedLine != "GO" && trimmedLine != "go") { commandText.Append(varLine); commandText.Append("\r\n"); } else { sqllist.Add(commandText.ToString()); commandText.Clear(); // 清空复用StringBuilder,避免重复创建 } } } if (commandText.Length > 0) { sqllist.Add(commandText.ToString()); } return sqllist; } catch (Exception ex) { // 建议添加日志记录,便于后续排查问题 return null; } }
2. 优化UpgradeDatabase函数
执行完SQL后主动清理引用,必要时触发GC回收(优先解决根本问题,GC触发为可选操作):
public static void UpgradeDatabase(DbHelper db, string deployPath) { try { string sqlPath = Path.Combine(deployPath, "Source", "UpdateDataBase.sql"); if (!System.IO.File.Exists(sqlPath)) { throw new FileNotFoundException("未找到UpdateDataBase.sql文件", sqlPath); } var sqlList = GetSqlList(sqlPath, db.Database); if (sqlList == null) { throw new Exception("解析SQL文件失败"); } db.ExecuteSqlList(sqlList); // 主动清空列表并解除引用 sqlList.Clear(); sqlList = null; // 可选:内存压力大时触发GC(不建议频繁调用) GC.Collect(); GC.WaitForPendingFinalizers(); } catch (Exception ex) { // 建议添加异常日志,避免吞掉异常导致问题无法定位 } }
3. 检查DbHelper.ExecuteSqlList实现
确认ExecuteSqlList中没有持有sqlList的引用,且数据库连接、命令等资源都通过using语句正确释放(比如SqlConnection、SqlCommand必须包裹在using中),这是常见的内存泄漏隐藏点。
额外建议
- 若该函数需要频繁执行,可缓存解析后的SQL列表,避免重复读取和解析文件。
- 使用Visual Studio的内存诊断工具抓取内存快照,查看未被回收的对象类型,精准定位泄漏点。
内容的提问来源于stack exchange,提问作者remilialover
相关产品推荐
相关产品推荐

