页内导航
‘关于数据库连接池大家都听说过或者用过,但真正的了解有多少呢?
- 数据连接池如何启用?有哪些主要的参数?
- 为什么要使用连接池?
- 如何关闭连接池?
- 如何在不开启新的连接池情况下切换当前数据库?
- 连接池的生命周期?
- 当数据库服务器强制关闭连接时会怎么样?
===========================================================================
首先说明一下测试环境:
数据库版本:SQL SERVER 12.0.2269.0 [Microsoft SQL Server 2014 Enterprise (64-bit)]
C#版本: Microsoft Visual C# 2015 14.0.25431
客户端版本:System.Data 4.0.0
===========================================================================
首先创建一个数据库访问类,便于测试:
public class DbAccepter { /// <summary> /// 数据库连接 /// </summary> private SqlConnection _conn = new SqlConnection(); /// <summary> /// 数据库连接字符串 /// </summary> private String _connectionString = ""; /// <summary> /// 数据库连接字符串 /// </summary> public string ConnectionString { get { return _connectionString; } set { _connectionString = value; _conn.ConnectionString = _connectionString; } } public DataTable GetData(string sql) { string sss = _conn.State.ToString(); using (SqlCommand cmd = new SqlCommand(sql, _conn)) { using (SqlDataAdapter adapter = new SqlDataAdapter(cmd)) { DataSet ds = new DataSet(); _conn.Open(); adapter.Fill(ds); _conn.Close(); return ds.Tables[0]; } } } }
1.如何开启连接池?
只需要连接字符串中加入对应的选项即可:
private static String _connectionString = "pooling=true;connection lifetime=5;min pool size = 2;max pool size=4;
Data Source = 127.0.0.1; Initial Catalog = tempdb; User ID = test; Password=Sm5lAXQiZ10L";
注意里面和连接池有关的参数:
pooling=true; --表示开启连接池(默认为开启)
min pool size = 2 --最小连接池大小:即什么也没执行初次连接的时候先和数据库服务建立n个连接
max pool size=4 --最大连接池大小:允许建立的最大连接数,是在需要的时候建立。
举例说明:min pool size = 2;max pool size=4 ;
点击按钮时调用以下代码执行数据库脚本。
try { info.Clear(); for (int i = 0; i < num.Value; i++) { Thread th = new Thread(GetCurrentDbName); th.Start(); } } catch (Exception ex) { listboxAdd(ex.Message); }
这里的GetCurrentDbName方法是创建数据库连接然后执行一段SQL脚本,获取当前数据库的名称然后延时3S,所以每次执行SQL的时间都约为3秒
select db_name(); waitfor delay \'00:00:03\'; --延迟3秒
这里我们先将并发数设为1,在初次建立连接时会创建2个连接。
可以在数据库中进行查看:
select * from sysprocesses where hostname=\'xxx\' and loginame=\'test\'
2. 那连接池是和什么有关呢?
在同一个进程中,只和连接字符串有关,只要连接字符串一样就会使用同一个连接池。
这里我们将连接字符串改为下图(只修改了最小数据池):
执行指令后,再观察一下数据库的连接信息:
我们发现会多出一个连接信息,可以怀疑是不是使用的同个连接池呢 ?。你可以再将并发数改为4,并再次执行
可以看到,这时的连接数变为6,之前的连接字符串占用了两个,修改后的占用了4个。因为连接池的大小限制为4,所以说明确实是使用了两个连接池。
当我们的并发请数大于最大连接池数会怎么样?这里我们修改一下之前的程序代码,记录线程调用方法执行的线程id和起止时间 :
var msg = $"线程【{Thread.CurrentThread.ManagedThreadId}】当前访问数据库:{dt.Rows[0][0].ToString()} {dt1.ToString("HH:mm:ss.fff")} ---- {DateTime.Now.ToString("HH:mm:ss.fff")}";
9个线程的执行截止时间,可以看出前四的截止时间基本相同,中间的四个大约比前4个晚3S。最后一个比中间四个晚3S。
说明开始有4个线程的数据库请求获取到连接池资源,其它线程等待。
这4个线程执行完成后,另4个线程获的连接池资源,最后一个线程等待真到再次释放连接池。
那我们再看一下数据库的连接数仍然为4。说明有多个并行请求时,超过连接池的部分将等待。等待多久会超时呢???
那么,我们可以查看SqlConnection.ConnectionTimeout 默认是15秒超时
结论是如果想使用连接池,必须使用相同的连接字符串,必须一字不差(我并没有全部测试)。
3.如何使用相同的连接池访问不同的数据库?
在实际开发中我们会访问同一个服务器中的多个数据库,但又不想创建过多的连接。如果访问不同的库使用不同的连接字符串,那就会产生多个连接池。
如这样:
pooling=true;connection lifetime=10;min pool size = 2;max pool size=4; Data Source = 127.0.0.1; Initial Catalog = tempdb; User ID = test; Password=Sm5lAXQiZ10L pooling=true;connection lifetime=10;min pool size = 2;max pool size=4; Data Source = 127.0.0.1; Initial Catalog = master; User ID = test; Password=Sm5lAXQiZ10L
这时有两个办法:
1.在SQL语句中指定数据库名称(不太靠谱)
2.不要重新创建连接,而是使用的相同的连接字符串创建连接后再切换数据库。实现代码如下:
using (SqlCommand cmd = new SqlCommand(sql, _conn)) { using (SqlDataAdapter adapter = new SqlDataAdapter(cmd)) { DataSet ds = new DataSet(); _conn.Open(); //这时指向的是tempdb _conn.ChangeDatabase("master"); //切换数据库 adapter.Fill(ds); _conn.Close(); return ds.Tables[0]; } }
4.数据库连接池的默认最大和最小值
默认数据库连接池到底有多大呢 ?
---------------------------------------------------------------------------------
测试环境:
Microsoft SQL Server Management Studio 11.0.2100.60
Microsoft Analysis Services 客户端工具 11.0.2100.60
Microsoft 数据访问组件 (MDAC) 10.0.17134.1
Microsoft MSXML 3.0 6.0
Microsoft Internet Explorer 9.11.17134.0
Microsoft .NET Framework 4.0.30319.42000
操作系统 6.3.17134
Microsoft Visual Studio Enterprise 2017 版本 15.9.3
VisualStudio.15.Release/15.9.3+28307.145
Microsoft .NET Framework 版本 4.5
已安装的版本: Enterprise
---------------------------------------------------------------------------------
同样,我们需要把上面的代码修改一下,让查询超时时间设置为17秒(超过默认超时时间)
string sql = "select db_name() as dbName;waitfor delay \'00:00:17\';";
我们把上面的程序重新运行,修改连接字符串:pooling=true;connection lifetime=5;Data Source = 127.0.0.1; Initial Catalog = TestDB; User ID = sa; Password=123456
并把线程数量改为110看看,并到数据库中再次执行查看运行结果,注意右下角是100行,如图
这时我们的程序也会报错,错误信息如下:
System.InvalidOperationException
HResult=0x80131509
Message=超时时间已到。超时时间已到,但是尚未从池中获取连接。出现这种情况可能是因为所有池连接均在使用,并且达到了最大池大小。
Source=System.Data
StackTrace:
在 System.Data.ProviderBase.DbConnectionFactory.TryGetConnection(DbConnection owningConnection, TaskCompletionSource`1 retry, DbConnectionOptions userOptions, DbConnectionInternal oldConnection, DbConnectionInternal& connection)
在 System.Data.ProviderBase.DbConnectionInternal.TryOpenConnectionInternal(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource`1 retry, DbConnectionOptions userOptions)
在 System.Data.ProviderBase.DbConnectionClosed.TryOpenConnection(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource`1 retry, DbConnectionOptions userOptions)
在 System.Data.SqlClient.SqlConnection.TryOpenInner(TaskCompletionSource`1 retry)
在 System.Data.SqlClient.SqlConnection.TryOpen(TaskCompletionSource`1 retry)
在 System.Data.SqlClient.SqlConnection.Open()
在 WindowsFormsApp1.Comm.DbAccepter.GetData(String sql) 在 D:\\_john\\LocalSvn\\TestSVN\\TestProject\\TestSqlConn\\WindowsFormsApp1\\Comm\\DbAccepter.cs 中: 第 58 行
在 WindowsFormsApp1.Form1.GetCurrentDbName() 在 D:\\_john\\LocalSvn\\TestSVN\\TestProject\\TestSqlConn\\WindowsFormsApp1\\Form1.cs 中: 第 63 行
在 System.Threading.ThreadHelper.ThreadStart_Context(Object state)
在 System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
在 System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
在 System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
在 System.Threading.ThreadHelper.ThreadStart()
从这些说明默认线程池的大小是100,并且程序在超过了线程数时,会因为无法从线程池中获取到连接而报异常。