“打开连接”实际上是什么意思?

17

我曾试图向某人解释为什么 数据库连接 实现了 IDisposable 接口,但是后来我意识到我其实不知道 "打开一个连接" 具体指什么。
因此我的问题是 - 当 C# 打开一个连接时,它实际上在做什么?

谢谢。


3
你是指数据库连接还是TCP连接?你需要提供更多细节。 - Kev
3个回答

29
实际上,在实现连接时涉及到两个类(其实更多,但我会简化)。
其中一个是您在代码中使用的 IDbConnection 实现(SQLConnection、NpgsqlConnection、OracleConnection 等)。另一个是“真实”的连接对象,它是内部程序集中的对象,不可见于您的代码。我们现在称之为“RealConnection”,尽管其实际名称因不同的实现而异(例如在 Npgsql 中,这是我最熟悉的实现情况,该类被称为 NpgsqlConnector)。
当您创建 IDbConnection 时,它没有 RealConnection。任何尝试与数据库进行交互的操作都将失败。然后,当您调用 Open() 时,会发生以下情况:
  1. 如果启用了池,并且池中有一个 RealConnection,请将其出列并将其设置为 IDbConnection 的 RealConnection。
  2. 如果启用了池,并且存在的 RealConnection 对象总数大于最大大小,则引发异常。
  3. 否则,创建一个新的 RealConnection。 对其进行初始化,这将涉及打开某种类型的网络连接(例如 TCP / IP)或文件句柄(例如Access),通过数据库的握手协议(因数据库类型而异)进行身份验证,然后将其设置为 IDbConnection 的 RealConnection。
对 IDbConnection 执行的操作将转换为 RealConnection 在其网络连接(或其他)上执行的操作。结果将转换为实现 IDataRecord 等接口的对象,以便为您的编程提供一致的接口。
如果使用 CommandBehavior.CloseConnection 创建了 IDataReader,则该数据读取器将“拥有” RealConnection。

当您调用Close()时,会发生以下情况之一:

  1. 如果使用连接池,并且池未满,则将对象放入队列以供稍后操作使用。
  2. 否则,RealConnection 将执行结束连接的任何协议定义程序(向数据库发出信号表示连接即将关闭)并关闭网络连接等。对象然后可能超出范围并可供垃圾回收。

例外情况是如果发生 CommandBehavior.CloseConnection 情况,则触发对 IDataReader 上的 Close()Dispose() 的调用。

如果调用Dispose(),则与Close()相同的事情会发生。不同之处在于,Dispose() 被认为是“清理”,可以与using一起使用,而 Close() 可能在生命周期中间使用,并在稍后跟随 Open()

由于使用了RealConnection对象并且它们被汇聚在一起,因此打开和关闭连接从相对较重的操作变为相对较轻的操作。因此,与其重要的是要保持长时间的连接以避免打开它们的开销,还不如将其保持尽可能短的时间,因为RealConnection 为您处理开销,并且您使用连接越快,汇聚的连接就会更高效地在多个使用之间共享。

请注意,即使您已经调用了 Close() 方法关闭了 IDbConnection 连接,在此之后再调用 Dispose() 方法也是可以的(事实上,规则是无论连接处于什么状态,甚至已经调用过 Dispose() 方法,都应该安全地调用它)。因此,如果您手动调用了 Close() 方法,将连接放入 using 块中仍然是一个好习惯,以便在调用 Close() 方法之前发生异常的情况下能够捕获异常。唯一的例外情况是您确实希望连接保持打开状态;例如,您正在返回使用 CommandBehavior.CloseConnection 创建的 IDataReader,此时您不需要处理 IDbConnection,但是需要处理读取器。
如果您未处理连接,则 RealConnection 将不会被返回到池中以供重用,也不会经过其关闭过程。池可能会达到限制,或者底层连接的数量会增加到破坏性能并阻止创建更多连接的程度。最终 RealConnection 上的清除程序可能会被调用并解决这个问题,但是清除程序只能减少损坏,不能依赖它。 (IDbConnection 不需要清除程序,因为是 RealConnection 持有未托管的资源和/或需要执行关闭)。
还可以合理地假设除了这个问题之外,IDbConnection 的实现中还存在其他处理要求,并且即使分析上述内容使您认为不需要进行处理,它仍应该被处理(例外情况是使用 CommandBehavior.CloseConnection 将所有处理负担传递给 IDataReader,但是这时处理读取器同样重要)。

1
非常好的回答,洞察力很强。+1 - RPM1984
2
@RPM1984 谢谢。我之前为 Npgsql 做出了一点贡献,包括 NpgsqlConnector 的工作原理,并从中学到了很多。它是一个相对较小的开源程序集,所以如果你认为这方面还有更多有趣的内容,可以去看看。 - Jon Hanna

4

很好的问题。

从我对SQL连接的“底层”工作的(有限)了解,涉及到许多步骤,例如:

底层步骤

  1. 打开物理套接字/管道(使用给定的驱动程序,例如ODBC)
  2. 与SQL Server握手
  3. 协商连接字符串/凭据
  4. 事务范围

更不用说连接池了,我认为这里涉及到某种算法(如果连接字符串与已经存在的池中的连接字符串匹配,则将连接添加到池中,否则创建新的连接)。

IDisposable

关于SQL连接,我们实现IDisposable,以便在调用dispose时(通过using指令或显式方式),它将连接放回连接池。这与只是简单的sqlConnection.Close()形成鲜明对比-因为所有这些都只是暂时关闭它,但保留该连接以供以后使用。

据我所知,.Close()会关闭与数据库的连接,而.Dispose()会调用.Close(),然后释放非托管资源。

考虑到这些要点,至少实现IDisposable是个好习惯。


不,Dispose 和 Close 的作用是相同的。我将在回答中详细说明。 - Jon Hanna
@Jon Hanna - 据我所知,不是这样的,我会找一篇文章来证明。 - RPM1984
据我理解,close仅关闭SQL连接,而dispose则调用close并释放非托管资源。不过我的理解可能有误。 - RPM1984
不需要猜测,打开Reflector查看即可。对于SqlConnection,Close和Dispose之间唯一的区别是Dispose会导致SqlConnection对象从其组件站点中移除(SqlConnection派生自Component)。当然,这只有在将SqlConnection对象添加到站点上时才有意义(例如,您将其拖放到窗体上)。 - Tergiver
如果正在使用连接池,则不要释放或关闭 SQL 连接,详见我的答案。Tergiver 在站点方面是正确的,我忘了这一点,因为我自己没有处理过。 - Jon Hanna

0

补充以上答案...关键在于“打开连接”时可能会分配资源,这些资源需要比标准垃圾回收更长的时间来恢复,即某种类型的打开套接字/管道/IPC。Dispose()方法可以清理这些资源。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接