数据库连接池

最近在开发项目的时候遇到由于连接池超时导致前端数据不显示的问题,经过发现是因为,在建立连接的过程中没有及时释放资源,导致连接池没有可用连接,从而不能相应前端请求造成超时,今天一起来看下关于Sql Server中连接池的使用。

什么是连接池?

建立一个数据库连接是一件非常耗时的过程,之所以是这样,是因为连接到数据库服务器需要经历几个漫长的过程: 建立物理通道(例如套接字或命名管道),与服务器进行初次握手,分析连接字符串信息,由服务器对连接进行身份验证,运行检查以便在当前事务中登记等等。如果每次建立都是很耗时耗力的话,是不是可以重复利用已有的连接呢?

因此连接池便出现了,连接池实际上是一个容器,在容器内存放了一定数量与数据库服务器的物理连接。因此,当我们需要连接数据库服务的时候,只需要从容器内取一条空闲的连接,而不是新建一个连接。将先前的连接保存下来,当下一次需要打开连接的时候就将先前的Connection交个下个链接。这样会大大减少连接数据库的开销,从而提高程序性能。

数据库Connection Pool连接池运行原理

当一个程序执行Connection.open()时候,ADO.net就需要判断,此连接是否支持Connection Pool (Pooling 默认为True)。

  • 若为False, ADO.net就与数据库之间创建一个连接,然后返回给程序。
  • 若为True,ADO.net就会根据ConnectString创建一个Connection Pool,然后向Connection Pool中填充Connection。填充多少个Connection由Min Pool Size (默认为0)属性来决定。例如如果指定为5,则ADO.net会一次与SQL数据库之间打开5个连接,然后将4个Connection,保存在 Connection Pool中,1个Connection返回给程序。

连接池1

当程序执行到Connection.close() 的时候。如果Pooling 为True,ADO.net 就把当前的Connection放到Connection Pool并且保持与数据库之间的连接。

下一次Connection.Open()的时候,ADO.NET就会判断ConnectionString与之前保存在连接池中的Connection的ConnectionString是否有一致。若一致进入同一个连接池,否则创建新的连接池。

ADO.NET需要判断当前的Connection Pool中是否有可以使用的Connection(没有被其他程序所占用),如果没有的话,ADO.NET就需要判ConnectionString设置的Max Pool Size (默认为100)

  • 如果Connection Pool中的所有Connection没有达到Max Pool Size,ADO.net则会再次连接数据库,创建一个连接,然后将Connection返回给程序。
  • 如果已经达到了 MaxPoolSize,ADO.NET就不会再次创建任何新的连接,新的连接请求将放置到连接队列中。当有连接释放给连接池时,连接池将新释放的连接分配给在队列中排队的连接请求。

连接池2

使用连接池

连接池的行为可以通过连接字符串来控制,主要包括四个重要的属性:

  • Connection Timeout:连接请求等待超时时间。默认为15秒,单位为秒。
  • Max Pool Size: 连接池中最大连接数。默认为100。
  • Min Pool Size: 连接池中最小连接数。默认为0。
  • Pooling: 是否启用连接池。ADO.NET默认是启用连接池的,因此,你需要手动设置Pooling=false来禁用连接池。

当用户打开一个连接而没有正确或者及时的关闭时,经常会引发”连接泄漏”的问题,泄露的连接会一直保持打开的状态,是的后续连接请求处于等待状态。因此需要注意及时的释放使用完的连接。

下例中,由于没有及时释放连接资源,导致程序跑出异常。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
using System;
using System.Data.SqlClient;

namespace SqlConnectionTester
{
class Program
{
private static SqlConnection conn;

static void Main(string[] args)
{
SqlConnectionStringBuilder connStr = new SqlConnectionStringBuilder();
connStr.DataSource = @".";
connStr.InitialCatalog = "Nuctech.NIS.OnlineTax";
connStr.IntegratedSecurity = false;
connStr.UserID = "sa";
connStr.Password = "123456";

connStr.MaxPoolSize = 5;//设置最大连接池为5
connStr.ConnectTimeout = 2;//设置超时时间为1秒

for (int i = 1; i <= 10; ++i)
{

var conn = new SqlConnection(connStr.ConnectionString);
try
{
conn.Open();
Console.WriteLine("Connection{0} is linked", i);
}
catch (Exception ex)
{
Console.WriteLine("\n异常信息:\n{0}", ex.Message);
break;
}

}

Console.Read();
}
}
}

未及时释放连接导致连接超时

不是显式关闭的连接可能不会添加或返回到池中。因此要使用Using关键字将连接资源包起来,或者当连接使用完之后,使用Connection对象的Close()Dispose()方法,垃圾回收器(GC)才关闭和释放连接,避免上述问题。

查看数据库连接数

  • 监视连接状态,不指定用户名则返回所有的Sql Server数据库连接

    1
    exec sp_who sa
  • 通过系统表来查询

    1
    select * from sysprocesses where dbid= db_id(数据库ID)
  • 查看已经使用的连接数个数

    1
    select count(*) from sys.dm_exec_connections

参考资料