数据库连接池
原文:http://www.cnblogs.com/liuhaorain/archive/2012/02/19/2353110.html
ADO.NET入门教程(五) 细说数据库连接池
题外话
通过前几章的学习,不知道大家对ADO.NET有一定的了解了没有。撇开文章质量不讲,必须肯定的是,我是用心去写每一篇文章的。无论是是在排版上,还是在内容选取上我都花了不少心思。我希望通过本系列文章,无论是新手还是老手,在ADO.NET上都能有所收获。如果大家觉得有帮助,我希望能得到您的推荐和关注,让我知道您对我的肯定。如果大家觉得我写的不好,我也很乐意听取批评的意见,让我们一起进步。
摘要
今天我要讲的是数据库连接池。说实话,我表示鸭梨很大。因为相比其他章节来说,连接池相对来说难理解一点。我要用最通俗的语句给大家讲明白,讲透彻却也不是一件很容易的事。但是,连接池又是非常重要的知识点,特别是在部署多用户程序时,显得尤为重要。所以,我不但要讲,而且要讲的透彻。通过本文,你将理解连接池的基本原理已经如何利用连接池来提高应用程序的性能。
目录
1. 什么是连接池?
在上篇文章《你必须知道的ADO.NET(四) 品味Connection对象》中,我已经强调过,建立一个数据库连接是一件非常耗时(消耗时间)耗力(消耗资源)的事情。之所以会这样,是因为连接到数据库服务器需要经历几个漫长的过程:建立物理通道(例如套接字或命名管道),与服务器进行初次握手,分析连接字符串信息,由服务器对连接进行身份验证,运行检查以便在当前事务中登记等等。我们先不管为什么会有这样的机制,存在总是有它的道理。既然新建一条连接如此痛苦,那么为什么不重复利用已有的连接呢?
实际上,ADO.NET已经为我们提供了名为连接池的优化方法。连接池就是这样一个容器:它存放了一定数量的与数据库服务器的物理连接。因此,当我们需要连接数据库服务器的时候,只需去池(容器)中取出一条空闲的连接,而不是新建一条连接。这样的话,我们就可以大大减少连接数据库的开销,从而提高了应用程序的性能。
PS:本来做了2张图片来描述连接池的,无奈公司装有监控软件,不能上传,所以只能等下次有时间上传了。
2. 连接池的工作原理
2.1 创建连接池
需要说明的是,连接池是具有类别区分的。也就是说,同一个时刻同一应用程序域可以有多个不同类型的连接池。那么,连接池是如何标识区分的?细致的讲,是由进程、应用程序域、连接字符串以及windows标识(在使用集成的安全性时)共同组成签名来标识区分的。但对于同一应用程序域来说,一般只由连接字符串来标识区分。当打开一条连接时,如果该条连接的类型签名与现有的连接池类型不匹配,则创建一个新的连接池。反之,则不创建新的连接池。
一个典型的创建连接的实例:
//创建连接对象1
using (SqlConnection conn1 =
new SqlConnection( "DataSource=(local);Integrated Security=SSPI;Initial Catalog=Northwind"))
{
conn1.Open();
}
//创建连接对象2
using (SqlConnection conn2 =
new SqlConnection( "DataSource=(local);Integrated Security=SSPI;Initial Catalog=pubs"))
{
conn2.Open();
}
//创建连接对象3
using (SqlConnection conn3 =
new SqlConnection( "DataSource=(local);Integrated Security=SSPI;Initial Catalog=Northwind"))
{
conn3.Open();
}
上面实例中,我创建了三个SqlConnection对象,但是管理时只需要两个连接池。细心的朋友,可能早已发现conn1与conn3的连接字符串相同,所以可以共享一个连接池,而conn2与conn1与conn3不同,所以需要创建新的连接池。
2.2 分配空闲连接
当用户创建连接请求或者说调用Connection对象的Open时,连接池管理器首先需要根据连接请求的类型签名找到匹配类型的连接池,然后尽力分配一条空闲连接。具体情况如下:
- 如果池中有空闲连接可用,返回该连接。
- 如果池中连接都已用完,创建一个新连接添加到池中。
- 如果池中连接已达到最大连接数,请求进入等待队列直到有空闲连接可用。
2.3 移除无效连接
无效连接,即不能正确连接到数据库服务器的连接。对于连接池来说,存储的与数据库服务器的连接的数量是有限的。因此,对于无效连接,如果如不及时移除,将会浪费连接池的空间。其实你不用担心,连接池管理器已经很好的为我们处理了这些问题。如果连接长时间空闲,或检测到与服务器的连接已断开,连接池管理器会将该连接从池中移除。
2.4 回收使用完的连接
当我们使用完一条连接时,应当及时关闭或释放连接,以便连接可以返回池中重复利用。我们可以通过Connection对象的Close或Dispose方法,也可以通过C#的using语句来关闭连接。
3. 说说几个非常重要属性
连接池的行为可以通过连接字符串来控制,主要包括四个重要的属性:
- Connection Timeout:连接请求等待超时时间。默认为15秒,单位为秒。
- Max Pool Size: 连接池中最大连接数。默认为100。
- Min Pool Size: 连接池中最小连接数。默认为0。
- Pooling: 是否启用连接池。ADO.NET默认是启用连接池的,因此,你需要手动设置Pooling=false来禁用连接池。
还是看一个实例来理解连接池的属性吧。代码如下:
SqlConnectionStringBuilder connStr = new SqlConnectionStringBuilder();
connStr.DataSource = @".\SQLEXPRESS";
connStr.InitialCatalog = "master";
connStr.IntegratedSecurity = true;
connStr.Pooling = true; //开启连接池
connStr.MinPoolSize = 0; //设置最小连接数为0
connStr.MaxPoolSize = 50; //设置最大连接数为50
connStr.ConnectTimeout = 10; //设置超时时间为10秒
using( SqlConnection conn = new SqlConnection(connStr.ConnectionString))
{
;//todo
}
4. 连接池异常与处理方法
当用户打开一个连接而没有正确或者及时的关闭时,经常会引发“连接泄露”问题。泄露的连接,会一直保持打开状态,直到调用Dispose方法,垃圾回收器(GC)才关闭和释放连接。与ADO不同,ADO.NET需要手动的关闭使用完的连接。一个重要的误区是:当连接对象超出局部作用域范围时,就会关闭连接。实际上,当超出作用域时,释放的只是连接对象而非连接资源。好吧,还是先看看一个实例吧。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.Data.SqlClient;
namespace ConnectionPool
{
class Program
{
static void Main(string[] args)
{
SqlConnectionStringBuilder connStr = new SqlConnectionStringBuilder();
connStr.DataSource = @".\SQLEXPRESS";
connStr.InitialCatalog = "master";
connStr.IntegratedSecurity = true;
connStr.MaxPoolSize = 5;//设置最大连接池为5
connStr.ConnectTimeout = 1;//设置超时时间为1秒
SqlConnection conn = null;
for (int i = 1; i <= 100; ++i)
{
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();
}
}
}
为了使结果更明显,我特地将最大连接数设置为5,超时时间为1秒。运行后,很快得到以下结果。
从上面的结果我们很明显的知道,连接出现了异常。我们已经知道连接池的最大连接数为5,当创建第6条连接时,由于连接池中连接数量已经达到了最大数并且没有空闲的连接,因此需要等待连接直到超时。当超过超时时间时,就出现了上述的连接异常。因此,我必须再次强调,使用完的连接应当尽快的正确的关闭和释放。
5. 监视SQL Server连接状态的方法
(1)通过活动监视器
第一步:打开MSSMS管理器,单击“活动监视器”图标。
第二步:在打开活动监视器视图中,单击“进程”选项卡。
第三步:运行 #4 连接池异常与处理方法 中的例子,则可以看到打开的5条连接,如下图所示。
(2)使用T-SQL语句
同样,通过执行系统存储过程sp_who,我们也可以监视连接状态。
exec sp_who
可得到以下结果:
6. 高效使用连接池的基本原则
用好连接池将会大大提高应用程序的性能。相反,如果使用不当的话,则百害而无一益。一般来说,应当遵循以下原则:
- 在最晚的时刻申请连接,在最早的时候释放连接。
- 关闭连接时先关闭相关用户定义的事务。
- 确保并维持连接池中至少有一个打开的连接。
- 尽力避免池碎片的产生。主要包括集成安全性产生的池碎片以及使用许多数据库产生的池碎片。
提示:池碎片是许多 Web 应用程序中的一个常见问题,应用程序可能会创建大量在进程退出后才会释放的池。 这样,将打开大量的连接,占用许多内存,从而导致性能降低。
java 实例:
摘自:百度知道
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266/*
数据库连接池
***********模块说明**************
getInstance()返回POOL唯一实例,第一次调用时将执行构造函数
构造函数Pool()调用驱动装载loadDrivers()函数;连接池创建createPool()函数
loadDrivers()装载驱动
createPool()建连接池
getConnection()返回一个连接实例
getConnection(long time)添加时间限制
freeConnection(Connection con)将con连接实例返回到连接池
getnum()返回空闲连接数
getnumActive()返回当前使用的连接数
*/
import java.sql.*;
public
class
Pool {
static
private
Pool instance =
null
;
//定义唯一实例
private
int
maxConnect = 100;
//最大连接数
private
int
normalConnect = 10;
//保持连接数
private
String password =
""
;
//密码
private
String url =
"jdbc:mysql://localhost:3306/DB"
;//连接URL
private
String user =
""
;
//用户名
private
String driverName =
""
;
//驱动类
Driver driver =
null
;
//驱动变量
DBConnectionPool pool =
null
;
//连接池实例变量
/**
* 返回唯一实例
* @return
*/
static
synchronized
public
Pool getInstance() {
if
(instance ==
null
) {
instance =
new
Pool();
}
return
instance;
}
/**
* 将构造函数私有,不允许外界访问
*/
private
Pool() {
loadDrivers(driverName);
createPool();
}
/**
* 装载和注册所有JDBC驱动程序
* @param dri
*/
private
void
loadDrivers(String dri) {
String driverClassName = dri;
try
{
driver = (Driver) Class.forName(driverClassName).newInstance();
DriverManager.registerDriver(driver);
System.
out
.println(
"成功注册JDBC驱动程序"
+ driverClassName);
}
catch
(Exception e) {
System.
out
.println(
"无法注册JDBC驱动程序:"
+ driverClassName +
",错误:"
+ e);
}
}
/**
* 创建连接池
*/
private
void
createPool() {
pool =
new
DBConnectionPool(password, url, user, normalConnect, maxConnect);
if
(pool !=
null
) {
System.
out
.println(
"创建连接池成功"
);
}
else
{
System.
out
.println(
"创建连接池失败"
);
}
}
/**
* 获得一个可用的连接,如果没有则创建一个连接,且小于最大连接限制
* @return
*/
public
Connection getConnection() {
if
(pool !=
null
) {
return
pool.getConnection();
}
return
null
;
}
/**
* 获得一个连接,有时间限制
* @param time
* @return
*/
public
Connection getConnection(
long
time) {
if
(pool !=
null
) {
return
pool.getConnection(time);
}
return
null
;
}
/**
* 将连接对象返回给连接池
* @param con
*/
public
void
freeConnection(Connection con) {
if
(pool !=
null
) {
pool.freeConnection(con);
}
}
/**
* 返回当前空闲连接数
* @return
*/
public
int
getnum(){
return
pool.getnum();
}
/**
* 返回当前连接数
* @return
*/
public
int
getnumActive(){
return
pool.getnumActive();
}
/**
* 关闭所有连接,撤销驱动注册
*/
public
synchronized
void
release() {
pool.release();
try
{
DriverManager.deregisterDriver(driver);
//撤销驱动
System.
out
.println(
"撤销JDBC驱动程序 "
+ driver.getClass().getName());
}
catch
(SQLException e) {
System.
out
.println(
"无法撤销JDBC驱动程序的注册:"
+ driver.getClass().getName());
}
}
}
//生成数据连接池
import java.sql.*;
import java.util.*;
import java.util.Date;
public
class
DBConnectionPool {
private
int
checkedOut;
private
Vector<Connection> freeConnections =
new
Vector<Connection>();
private
int
maxConn;
private
String password;
private
String url;
private
String user;
private
static
int
num=0;
//空闲的连接数
private
static
int
numActive=0;
//当前的连接数
public
DBConnectionPool(String password, String url, String user,
int
normalConn,
int
maxConn) {
this
.password = password;
this
.url = url;
this
.user = user;
this
.maxConn = maxConn;
for
(
int
i = 0; i < normalConn; i++) {
//初始normalConn个连接
Connection c = newConnection();
if
(c !=
null
)
{freeConnections.addElement(c);num++;}
}
}
/**
* 释放不用的连接到连接池
*/
public
synchronized
void
freeConnection(Connection con) {
freeConnections.addElement(con);
num++;
checkedOut--;
numActive--;
notifyAll();
}
/**
* 获取一个可用连接
* @return
*/
public
synchronized Connection getConnection() {
Connection con =
null
;
if
(freeConnections.size() > 0) {
//还有空闲的连接
num--;
con = (Connection) freeConnections.firstElement();
freeConnections.removeElementAt(0);
try
{
if
(con.isClosed()) {
System.
out
.println(
"从连接池删除一个无效连接"
);
con = getConnection();
}
}
catch
(SQLException e) {
System.
out
.println(
"从连接池删除一个无效连接"
);
con = getConnection();
}
}
else
if
(maxConn == 0 || checkedOut < maxConn) {
//没有空闲连接且当前连接小于最大允许值,最大值为0则不限制
con = newConnection();
}
if
(con !=
null
) {
//当前连接数加1
checkedOut++;
}
numActive++;
return
con;
}
/**
* 获取一个连接,并加上等待时间限制,时间为毫秒
* @param timeout
* @return
*/
public
synchronized Connection getConnection(
long
timeout) {
long
startTime =
new
Date().getTime();
Connection con;
while
((con = getConnection()) ==
null
){
try
{
wait(timeout);
}
catch
(InterruptedException e) {}
if
((
new
Date().getTime() - startTime) >= timeout) {
System.
out
.println(
"超时了!!"
);
return
null
;
//超时返回
}
}
return
con;
}
/**
* 关闭所有连接
*/
@SuppressWarnings(
"unchecked"
)
public
synchronized
void
release() {
Enumeration allConnections = freeConnections.elements();
while
(allConnections.hasMoreElements()) {
Connection con = (Connection) allConnections.nextElement();
try
{
con.close();
num--;
}
catch
(SQLException e) {
System.
out
.println(
"无法关闭连接池中的连接"
);
}
}
freeConnections.removeAllElements();
numActive=0;
}
/**
* 创建一个新连接
* @return
*/
private
Connection newConnection() {
Connection con =
null
;
try
{
if
(user ==
null
) {
//用户,密码都为空
con = DriverManager.getConnection(url);
}
else
{
con = DriverManager.getConnection(url, user, password);
}
System.
out
.println(
"连接池创建一个新的连接"
);
}
catch
(SQLException e) {
System.
out
.println(
"无法创建这个URL的连接"
+ url);
return
null
;
}
return
con;
}
/**
* 返回当前空闲连接数
* @return
*/
public
int
getnum(){
return
num;
}
/**
* 返回当前连接数
* @return
*/
public
int
getnumActive(){
return
numActive;
}
}
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 字符编码:从基础到乱码解决