C# 连接池

目录

1、数据库驱动

2、 ADO.NET、ADO、OLEDB、ODBC

3、 连接池

4、 池的创建和分配

5、 移除连接

6、 以Oracle为例,进行代码演示

6.1 池的创建

6.2 创建连接

6.3 将连接返还给池

6.4 释放连接池

6.5 演示:创建多余2个连接会是什么情况?

6.6 演示:关闭一个连接后,等待的连接会自动连接

6.7 演示:等待超时后并不会自动连接

6.8 演示:同一个进程中,连接字符串一样的连接共享连接池

6.9 演示:非同一进程,连接字符串一样的连接不共享连接池

7、总结


1、数据库驱动

数据库驱动是不同数据库开发商(比如oracle mysql等)为了某一种开发语言环境(比如C#、java等)能够实现统一的数据库调用而开发的一个程序接口。

以Oracle为例:安装了.Net FrameWork 后如果不安装Oracle的客户端,应用程序是无法连接Oracle数据库的,因为.Net的数据库访问API连接oracle时需要调用Oracle的客户端数据库访问驱动,例如我们经常出现的Oracle.DaraAccess.dll之类。

2、 ADO.NET、ADO、OLEDB、ODBC

发展史:ODBC->OLEDB->ADO->ADO.Net

ODBC:最早的通用数据访问接口,但是只支持访问关系型数据库。

OLEDB:通向不同的数据源的低级应用程序接口,OLE DB不仅包括微软资助的标准数据接口开放数据库连通性(ODBC)的结构化查询语言(SQL)能力,还具有面向其他非SQL数据类型的通路。

ADO:是OLEDB的简化版本,实在OLEDB接口之上附加的一层,简化了OLEDB的调用。

ADO.NET:ADO.NET在ADO的基础上进行了扩展和变化,随着支持的数据库类型越来越多、应用的语言特性越来越多譬如Linq to Dataset、实体对象模型的框架等变化太多就被认为是一个新的产品。ADO.Net 是一组向 .NET Framework 程序员公开数据访问服务的类。ADO.NET 提供对诸如 SQL Server 和 XML 这样的数据源以及通过 OLE DB 和 ODBC 公开的数据源的一致访问。

我的总结:可以认为这一票都是微软在数据访问功能方面设计和封装的一系列编程接口(API)。ODBC是微软关系数据库访问标准,以此标准封装了ODBC、OLEDB这些微软偏底层的接口,调用和操作比较复杂,ADO、ADO.NET是对ODBC、OLEDB的封装和扩展,是高级接口,便于程序员调用。

3、 连接池

连接到数据源可能需要很长时间。 打开连接的成本降到最低,ADO.NET 使用称为的优化方法连接池,其中重复打开和关闭连接的成本降至最低。 .NET Framework 数据提供程序处理连接池的方式有所不同。

连接池使新连接必须打开的次数得以减少。 池进程保持物理连接的所有权。 通过为每个给定的连接配置保留一组活动连接来管理连接。 每当用户在连接上调用 Open 时,池进程就会查找池中可用的连接。 如果某个池连接可用,会将该连接返回给调用者,而不是打开新连接。 应用程序在该连接上调用 Close 时,池进程会将连接返回到活动连接池集中,而不是关闭连接。 连接返回到池中之后,即可在下一个 Open 调用中重复使用。

4、 池的创建和分配

在初次打开连接时,将根据完全匹配算法创建连接池,该算法将池与连接中的连接字符串关联。 每个连接池都与一个不同的连接字符串相关联。 打开新连接时,如果连接字符串并非与现有池完全匹配,将创建一个新池。 按进程、应用程序域、连接字符串以及 Windows 标识(在使用集成的安全性时)来建立池连接。 连接字符串还必须是完全匹配的;按不同顺序为同一连接提供的关键字将分到单独的池中。

5、 移除连接

如果空闲时间达到大约 4-8 分钟,或池进程检测到与服务器的连接已断开,连接池进程会将该连接从池中移除。 注意,只有在尝试与服务器进行通信之后才能检测到断开的连接。 如果发现某连接不再连接到服务器,则会将其标记为无效。 无效连接只有在关闭或重新建立后,才会从连接池中移除。

如果存在一个与已消失的服务器的连接,即使连接池进程尚未检测到断开的连接,也可以从池中取出此连接并将连接标记为无效。 这种情况是因为检查连接是否仍有效的系统开销将造成与服务器的另一次往返,从而抵消了池进程的优势。 发生此情况时,初次尝试使用该连接将检测连接是否曾断开,并引发异常。

6、 以Oracle为例,进行代码演示

6.1 池的创建

初次创建连接时,.Net会自动依据连接创建对应的连接池。

 

6.2 创建连接

将池的最大和最小连接数都设置为2。

  try
            {
                m_OrclConnection = new OracleConnection();
                string sConenctStr = "Persist Security Info = True; Data Source = {0};"
                                    + "User ID = {1}; Password ={2};"
                                    + "Pooling=true;Max Pool Size=2;Min Pool Size=2;";
                sConenctStr = string.Format(sConenctStr, "ORCL_xxxxxxx", "XXXX", "XXXX");
                m_OrclConnection.ConnectionString = sConenctStr;
                Action actionConectStart = () =>
                  {
                      mmeEdit.Text += Environment.NewLine + DateTime.Now.Minute + "分" + DateTime.Now.Second + "秒";
                      mmeEdit.Text += Environment.NewLine + string.Format("开始打开数据库连接...", m_ListConn.Count);
                  };

                Action actionConnecting = () =>
                {
                    mmeEdit.Text += Environment.NewLine + string.Format("正在等待打开数据库连接...");
                };

                Action<string> actionConectDone = (time) =>
                {
                    mmeEdit.Text += Environment.NewLine + DateTime.Now.Minute + "分" + DateTime.Now.Second + "秒";
                    mmeEdit.Text += Environment.NewLine + string.Format("已经打开1个连接,耗时{0}毫秒...", time);
                    mmeEdit.Text += Environment.NewLine;
                };

                //// 为了不阻塞UI线程,异步执行
                Task.Run(() =>
                {
                    mmeEdit.Invoke(actionConectStart);
                    DateTime dateBegin = DateTime.Now;
                    m_OrclConnection.OpenAsync();       // 如果超过连接池最大数,此处会阻塞
                    var consumeTime = DateTime.Now - dateBegin;
                    if (m_OrclConnection.State == ConnectionState.Open)
                    {
                        mmeEdit.Invoke(actionConectDone, new object[] { consumeTime.Milliseconds.ToString() });
                    }
                    else if (m_OrclConnection.State == ConnectionState.Connecting)
                    {
                        mmeEdit.Invoke(actionConnecting);
                    }
                });

                m_ListConn.Add(m_OrclConnection);
                mmeEdit.Text += Environment.NewLine + string.Format("共创建了{0}个连接...", m_ListConn.Count);
            }
            catch (Exception ex)
            {
                mmeEdit.Text += ex.Message;
            }

6.3 将连接返还给池

使用Close方法时,并不会关闭连接,而是将连接归还给连接池管理。

if (m_ListConn.Count > 0)
{
                    var openConn = m_ListConn.First((item) => item.State ==ConnectionState.Open);                        
                    if (openConn == null)
                    {
                        mmeEdit.Text += Environment.NewLine + "无打开的连接...";
                    }
                    else
                    {
                        mmeEdit.Text += Environment.NewLine;
                        openConn.Close();
                        if (openConn.State == ConnectionState.Open)
                            mmeEdit.Text += Environment.NewLine + "关闭连接失败...";
                        else
                            mmeEdit.Text += Environment.NewLine + "关闭连接成功...";
                    }
                }

6.4 释放连接池

 if (m_OrclConnection != null)
            {
                //// 释放当前连接的连接池
                OracleConnection.ClearPool(m_OrclConnection);
                mmeEdit.Text += Environment.NewLine + "清除当前池成功..." + Environment.NewLine;
                m_ListConn.Clear();
            }
 if (m_OrclConnection != null)
            {
                //// 释放进程的所有连接池
                OracleConnection.ClearAllPools();
                mmeEdit.Text += Environment.NewLine + "清除所有池成功..." + Environment.NewLine;
                m_ListConn.Clear();
            }

6.5 演示:创建多余2个连接会是什么情况?

6.6 演示:关闭一个连接后,等待的连接会自动连接

6.7 演示:等待超时后并不会自动连接

等待的时间是有限制的,超时后不会自动连接。

6.8 演示:同一个进程中,连接字符串一样的连接共享连接池

打开一个主窗体,在主窗体中打开一个子窗体,在主窗体中先打开两个连接,然后再在子窗体中打开连接会发现打不开连接,说明其共享一个连接池。

备注:子窗体的代码和主窗体一模一样。

 

6.9 演示:非同一进程,连接字符串一样的连接不共享连接池

打开两个应用程序,都打开两个连接。

7、总结

  • 连接池充当了对活动连接的维护作用,我们代码Close连接后并不会关闭连接而是归还给连接池以供其他逻辑使用,这样我们每次打开连接并不需要建立物理链路,连接登录数据库实例等长时间的连接操作,而是有效的利用了连接池中活动的连接。
  • 连接池的创建比较严格,根据应用程序进程、应用程序域、连接字符串等严格匹配创建,参数顺序不同也会创建不同的连接池。
  • 等待连接的连接是由时间限制的,等待超时需要重新Open。
  • 单线程的进程中,如果是对同一数据库,用同一用户登录,只需创建一个连接,使用之后Close掉,归还给连接池。

 

参考地址:

https://www.cnblogs.com/liuzhendong/archive/2012/01/29/2331189.html

https://docs.microsoft.com/zh-cn/dotnet/framework/data/adonet/

https://www.cnblogs.com/wangcq/p/3614276.html

https://docs.microsoft.com/zh-cn/dotnet/framework/data/adonet/ole-db-odbc-and-oracle-connection-pooling

 

 

### C#连接池的使用方法与原理 #### 1. 连接池的工作机制 在 C# 中,连接池是一种优化技术,用于减少频繁创建和销毁数据库连接所带来的性能开销。通过维护一组预先建立好的连接实例,连接池可以在多次请求之间复用这些连接,从而提高应用的整体效率。 当调用 `Open` 方法时,连接池会尝试从现有的池中找到一个未被占用的连接并将其提供给调用方[^2]。如果找不到合适的连接,则会新建一个连接加入到池中。同样,在调用 `Close` 或 `Dispose` 方法时,实际并不会真正关闭连接,而是将它放回连接池供后续使用。 #### 2. 配置连接字符串支持连接池 为了启用连接池功能,默认情况下大多数 ADO.NET 数据库驱动程序都会自动开启这一特性。然而,也可以显式地调整一些参数来自定义行为: ```csharp string connectionString = "Server=myServerAddress;Database=myDataBase;" + "User Id=myUsername;Password=myPassword;" + "Pooling=true;Min Pool Size=5;Max Pool Size=20;"; ``` 上述代码片段展示了如何配置基本的 MySQL 数据库连接字符串以激活连接池,并设置了最小 (`Min Pool Size`) 和最大 (`Max Pool Size`) 的连接数量限制[^4]。 - **Pooling**: 设置为 true 表示启用了连接池;false 则禁用。 - **Min Pool Size & Max Pool Size**: 定义了初始以及上限允许存在的空闲连接数目。 另外还可以指定超时选项如 Connect Timeout 来控制等待可用资源的最大秒数[^3]。 #### 3. 处理特殊情况下的错误 即使有了良好的设计模式,仍然可能出现某些极端情况比如所有连接都被占满而无法立即获取新的实例。此时如果没有妥善处理可能会抛出 InvalidOperationException 异常消息提示“超出最大并发连接”。对此建议采取以下措施之一应对: - 增加 Maximum Pool Size 参数值扩大容量; - 调整 Connection Lifetime 属性缩短单次借用周期以便更快释放资源; - 实现异步查询逻辑降低阻塞概率并通过 Asynchronous Processing=True 启动非同步执行路径。 对于那些确实不需要长期维持持久化链接的任务场景来说,记得适时清理无用对象非常重要。可以借助 SqlConnection 类型提供的静态函数 ClearPool 或者 ClearAllPools 达成目的[^1]: ```csharp // 清除特定连接对应的单一池子 SqlConnection.ClearPool(connection); // 删除当前进程中所有的SQL Server相关联列队 SqlConnection.ClearAllPools(); ``` 以上两种方式都可以有效回收内存空间防止泄露风险发生。 ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值