System.Data.SqlClient老版本引发内存溢出的问题

有一个朋友和我说他负责的项目有内存溢出的情况,内存一直居高不下达到了8G。生产环境碰到内存溢出的情况我还没有碰到过,这次有机会实践一下,准备试试,拿到了dump文件,压缩包300M,解压出来3G。该事件是发生在2023年,写这篇文件的是已经是2024年了,为什么会间隔这么久才写这篇文章,因为当时没想到要写文章记录一下,但是后面想写的时候发现dump文件不见了,最近在整理电脑资料的时候发现了这个大文件。

1. 用VS查看dump

1.1 通过VS打开dump

image

1.2 选择右侧的调试托管内存

image

image

看计数和大小以及非独占大小确实存活的对象好多,并且占用的内存也不小。

1.3 分析这些没有被GC回收的对象

image

image

image
image

通过查看前几个对象发现,最终都指向到同一个类System.Data.SqlClient.SNI.SNIMarsManager里面的一个静态变量Singleton,看到这里的时候还很疑惑,这不是微软的库嘛,有内存溢出的问题不该早解决了。

1.4 查找System.Data.SqlClient.SNI.SNIMarsManager该类的相关资料

确实在GitHub上面找到了一个说自己写一个了web api链接sqlserver并且启用了MARS会导致产生了内存但没有释放,这里的疑惑在于该问题已于2017年解决了,现在的项目怎么也不可能用2017年的版本SqlClient。

image

Memory overconsumption on SqlClient with MARS #22949

image

  1. 查看System.Data.SqlClient该库源码看看
    通过反编译工具查看已经没有SNIMarsManager这个类了。
    image

接着继续找该类的资料,后面发现.NET Core 2.0 November Update - November 14, 2017这里有一个提交记录就是和该类相关的,这个时候才反映过来了为什么这个类找不到,原来类被删了
image
通过提交记录找到了提交之前的代码,该类源码如下:

using System.Collections.Concurrent;
using System.Collections.Generic;

namespace System.Data.SqlClient.SNI
{
    /// <summary>
    /// Singleton to manage all MARS connection
    /// </summary>
    internal class SNIMarsManager
    {
        public static readonly SNIMarsManager Singleton = new SNIMarsManager();
        private ConcurrentDictionary<SNIHandle, SNIMarsConnection> _connections = new ConcurrentDictionary<SNIHandle, SNIMarsConnection>();

        /// <summary>
        /// Constructor
        /// </summary>
        public SNIMarsManager()
        {
        }

        /// <summary>
        /// Create a MARS connection
        /// </summary>
        /// <param name="lowerHandle">Lower SNI handle</param>
        /// <returns>SNI error code</returns>
        public uint CreateMarsConnection(SNIHandle lowerHandle)
        {
            SNIMarsConnection connection = new SNIMarsConnection(lowerHandle);

            if (_connections.TryAdd(lowerHandle, connection))
            {
                return connection.StartReceive();
            }
            else
            {
                return TdsEnums.SNI_ERROR;
            }
        }

        /// <summary>
        /// Get a MARS connection by lower handle
        /// </summary>
        /// <param name="lowerHandle">Lower SNI handle</param>
        /// <returns>MARS connection</returns>
        public SNIMarsConnection GetConnection(SNIHandle lowerHandle)
        {
            return _connections[lowerHandle];
        }
    }
}

通过源码可以很明显的看到该类有一个静态变量Singleton,而且只有添加没有删除,这就导致MARS连接越来越多的时候内存越来越大,并且没有释放,导致程序运行占了8G,到这里基本上就找到问题的原因了,从晚上七点半一直到十一点,可以睡个好觉了。

3. 升级System.Data.SqlClient

第二天告知朋友升级System.Data.SqlClient,老版本确实存在内存泄漏的这个问题。朋友告诉我他用的库版本号是4.2。
image
我说你升级到最新就行了。但是这个时候奇葩的问题来了,项目找不到该库,我让他程序集和nuget引用都找找,最后定位到hangfire这个库,这是一个定时任务的库,该库可以使用sqlserver存储,有一个单独库叫Hangfire.SqlServer,在老版本安装的时候会自动安装System.Data.SqlClient,System.Data.SqlClient在Hangfire.SqlServer的依赖里面。升级一下Hangfire.SqlServer就解决了。

4. 总结

Bug出现在老版本的System.Data.SqlClient,然而ORM使用老版本的Hangfire.SqlServer的依赖库System.Data.SqlClient。过了一个月再问确实是没有再出现内存溢出的问题了,当时想着问题解决了就好,并没有想着记录,后面才发现这种机会难得还是需要记录一下。

posted @ 2024-10-30 09:36  长空nice  阅读(75)  评论(0)    收藏  举报