为什么如果在SqlHelper.Conn里open(),连接池会有2个或3个连接呢?
如果在using里再open(),则只有一个连接。
不用using也是一样的情况。
ORM用的是Dapper
你是说使用你贴图中的 SqlHelper 类,写下如下代码:
var conn = SqlHelper.Conn;
执行完此条语句后,连接池中有 2 - 3 个连接,是这个意思吗?
using(var conn = SqlHepler.Conn)
这样就会有,不用using就是一条
@Mango_: 你新建一个 Console 程序,然后写如下代码:
using(var conn = SqlHelper.Conn)
{
}
Console.Read();
不要关闭窗口,然后看看连接池中有几条连接?
@Launcher: 好奇怪,用公司的电脑怎么弄都只有一条
@Mango_: 你是说你按照我的建议写了一个 Console 程序,在公司电脑运行就只有一条,但是拷贝到不是公司的电脑上运行,就是2-3条?
@Launcher: console和单元测试在公司电脑只有一条,家里电脑只用了单元测试。
@Mango_: 你把我建议你写的 Console 程序拿回家去运行,再出现2-3条连接,你再过来问。因为我觉得你的代码有 BUG,或者你不知道你的单元测试代码的实际运行是怎样的;因此你一定要使用我建议你写的程序回家测试。
@Launcher: 我回家试试,晚上再来更新
@Launcher: 我在console上试了好几遍,只出现一条;但是在单元测试上又试了好几遍,有时候一条,有时候两条,或者三条,大部分时候1条。所以不确定console上是没试出来,还是只会有一条。
@Mango_: 首先你应该不会怀疑是 .Net 平台的 BUG 了吧!在此前提下,我假设你的代码也没有 BUG,那么最有可能的原因就是你的“单元测试”是以多线程模式运行的。
@Launcher: 能否给个联系方式,我把demo发给你看看。
@Mango_: 如果可以你还是把代码精简下贴这里吧!
@Launcher: 我修改问题,用到的代码都贴出来了
@Mango_: 你如何调用的?“单元测试”的具体操作步骤是啥?
@Launcher: 图片顺序就是调用顺序,含Assert的方法就是单元测试所有的代码了
@Mango_: 我给你解决问题都有耐心,结果你还没耐心。
你是不是用 VS 的测试工具,建立一个单元测试项目,然后添加了一个测试方法:
void TestMethod()
{
using(var conn = SqlHelper.Conn)
{
var number = conn.Find<Number>(1);
Assert.AreEqual(ConnectionState.Open,conn.State);
}
}
接着你在 VS 菜单中选择启动测试。
那么,请问你建立测试项目时的参数都是如何设置的?
@Launcher: 抱歉。
是这样的,我以为里面有个Assert大家能明白那个方法是测试方法,而且代码很简单,我就没特别说明调用顺序了。
你指的参数是什么?建测试项目没有地方需要配置参数呀。
@Mango_: 大哥,我不像你这么聪明,我很笨的,所以你得告诉我,你是如何建立测试项目的,你每一步怎么做的,不然我没办法告诉你为什么。
@Launcher: 建个测试项目,把using段放在TestMethod()里,就没了啊,还需要什么配置?
@Mango_: 把 IDbConnection 上的 Query<T> 这个扩展方法的代码贴出来。
@Launcher: 这个是调用ORM工具Dapper的方法 https://github.com/StackExchange/dapper-dot-net
@Mango_: 我不用 Dapper,我就不看了,我给你测试代码,将你的 TestMethod 中方法修改成这样:
using(var conn = SqlHelper.Conn)
{
var number = conn.Find<Number>(1);
Assert.AreEqual(ConnectionState.Open,conn.State);
// 下面的代码用于检测实际创建的连接数,虽然也可以通过 perfmon 查看。
Type sqlConnType = typeof(SqlConnection);
FieldInfo _poolGroupFieldInfo = sqlConnType.GetField("_poolGroup", BindingFlags.NonPublic | BindingFlags.Instance);
object dbConnectionPoolGroup = _poolGroupFieldInfo.GetValue(conn);
FieldInfo _poolCollectionFieldInfo = dbConnectionPoolGroup.GetType().GetField("_poolCollection", BindingFlags.NonPublic | BindingFlags.Instance);
HybridDictionary poolConnection = _poolCollectionFieldInfo.GetValue(dbConnectionPoolGroup) as HybridDictionary;
foreach (DictionaryEntry poolEntry in poolConnection)
{
object foundPool = poolEntry.Value;
FieldInfo _objectListFieldInfo =
foundPool.GetType().GetField("_objectList", BindingFlags.NonPublic | BindingFlags.Instance);
object listTDbConnectionInternal = _objectListFieldInfo.GetValue(foundPool);
MethodInfo get_CountMethodInfo =
listTDbConnectionInternal.GetType().GetMethod("get_Count");
object numConnex = get_CountMethodInfo.Invoke(listTDbConnectionInternal, null);
Assert.AreEqual(Int.Parset(numConnex.ToString(),1);
}
}
然后“运行”此测试方法。
我观察了下你查看连接的截图,你应该使用了 SP_WHO 命令,记得将可用数据库更换为 master。
@Launcher: 你好,我试了,poolConnection 为null。
我改了两处地方
@Mango_: 你使用的是 .Net 4.x 版本吧!换下面这段代码,所有版本适用。
Type sqlConnType = typeof(SqlConnection);
FieldInfo innerConnectionFieldInfo = sqlConnType.GetField("_innerConnection", BindingFlags.NonPublic | BindingFlags.Instance); object _innerConnection = innerConnectionFieldInfo.GetValue(conn);
FieldInfo connectionPoolFieldInfo = _innerConnection.GetType().BaseType.BaseType.GetField("_connectionPool", BindingFlags.NonPublic | BindingFlags.Instance); object _connectionPool = connectionPoolFieldInfo.GetValue(_innerConnection);
FieldInfo totalObjectsFieldInfo = _connectionPool.GetType().GetField("_totalObjects", BindingFlags.NonPublic | BindingFlags.Instance); object _totalObjects = totalObjectsFieldInfo.GetValue(_connectionPool);
Assert.AreEqual((int)_totalObjects,1);
@Launcher: 是的,4.5。 _totalObjects 这个变量是指连接数吗?
@Mango_: 对,连接数。
@Launcher: 好的,等晚上回去试试家里电脑,公司电脑试了几次都是1。
@Launcher: 无语,在家里电脑上测了一二十次,都没有问题,sp_who也只能看到1次了。
@Mango_: 你可以建立一个“负载测试项目”,设定为 1 用户,运行 1 到 2 小时。
@Launcher: 还有这种测试?我去查查资料。
@Mango_: 你新建测试项目的时候,就有三个测试项目类型,其中一个就是负载测试。
@Launcher: 没有啊,只有一个单元测试,我的是vs2013 pro版,可能没有那个。
@Mango_: 你的版本太低,换 Ultimate 。
@Launcher: 嗯
@Launcher: 你好,我运行了20分钟负载测试,测试总数20多万,都没通过,每个_totalObjects都是25。
@Mango_: 请问,你负载测试设置的参数是多少?
@Launcher: 都是按照默认的,是5分钟,不是20分钟
@Mango_: 你这人真懒,啥都要别人帮你做,就因为你一句“都是按照默认的”,我还得自己创建一个负载测试项目查看默认参数。负载测试默认负载模式是“常量负载”,“用户计数”默认为 25 用户,同你的观测结果“每个_totalObjects都是25”是一致的,因此 ADO.NET 的连接池管理,以及你编写的代码都是正确的。
@Launcher: 不好意思,我以为你熟悉负载测试,知道默认参数是什么,所以我就这么说的。真是麻烦你了!
连接到数据库服务器通常由几个需要很长时间的步骤组成。 必须建立物理通道(例如套接字或命名管道),必须与服务器进行初次握手,必须分析连接字符串信息,必须由服务器对连接进行身份验证,必须运行检查以便在当前事务中登记,等等。
实际上,大多数应用程序仅使用一个或几个不同的连接配置。 这意味着在执行应用程序期间,许多相同的连接将反复地打开和关闭。 为了使打开连接花费的系统开销最小,ADO.NET 使用称为连接池的优化方法。
连接池使新连接必须打开的次数得以减少。 池进程保持物理连接的所有权。 通过为每个给定的连接配置保留一组活动连接来管理连接。 每当用户在连接上调用 Open 时,池进程就会查找池中可用的连接。 如果某个池连接可用,会将该连接返回给调用者,而不是打开新连接。 应用程序在该连接上调用 Close 时,池进程会将连接返回到活动连接池集中,而不是关闭连接。 连接返回到池中之后,即可在下一个 Open 调用中重复使用。
只有配置相同的连接可以建立池连接。 ADO.NET 同时保留多个池,每种配置各一个。 在使用集成的安全性时,连接按照连接字符串以及 Windows 标识分到多个池中。 还根据连接是否已在事务中登记来建立池连接。 在使用 ChangePassword 时,SqlCredential 实例影响连接池。 SqlCredential 的不同实例将使用不同的连接池,即使用户 ID 和密码相,也是如此。
池连接可以显著提高应用程序的性能和可缩放性。 默认情况下,在 ADO.NET 中启用连接池。 除非显式禁用,否则,在应用程序中打开和关闭连接时,池进程会对连接进行优化。 还可以提供几个连接字符串修饰符来控制连接池的行为。 有关更多信息,请参见本主题后面的“使用连接字符串关键字控制连接池”。
----摘自MSDN SQL Server 连接池 (ADO.NET)
我这个是初次运行,且只运行一次啊,按理说应该只有一个才对啊
@Mango_: 理解一下上面说的。连接池初始化就会创建多个连接(根据Min Pool Size参数),当调用Open时就找到可用的连接拿出来用,用完Close时放回到连接池。
相当于,饭店生意好,吃饭的人多,所以我先买了一些碗放家里,有个人吃饭就拿个出来,用完了洗干净放回去;而不是到来一个客人,去买一个碗,而买碗的过程是比较麻烦的。
可能这个比喻不完全恰当,但就是这个意思。
@liqipeng: 但是如果open在using里面,我多复制了几个using执行,数据库里也只有一个连接。