|
概述:
数据库连接池允许应用程序重用已存在于池中的数据库连接,以避免反复的建立新的数据库连接。这种技术能有效提高应用程序的伸缩性,因为有限的数据库连接能够给大量的客户提供服务。这种技术同时也提高的系统性能,避免了大量建立新连接的开销。
开发一个具有伸缩性的、高性能应用程序应该最大限度的减少建立连接所花费的时间,保持数据库连接最大限度的有效,以存取数据。当一个数据库连接关闭时,它只是由连接池收回以待重用,并未真正释放。但是,如果连接池被释放,数据库连接将会被释放掉。
开发人员应当注意不要依赖垃圾回收机制去释放数据库连接,因为当参数超出作用域时,数据库连接并没有得必要的关闭,这种数据库资源泄漏将导致建立新连接时抛出连接错误。
建立数据库连接池
当打开一个数据库连接时,一个数据库连接池也就创建了。数据库连接池的创建与数据库连接字符串精确的相关(包括空格、大小写)。所有的连接池是根据连接字符串来区分的。在创建一个新的数据库连接时,如果连接字符串不完全相同,将创建不同的连接池。
一旦数据库连接池被创建,它将一直存在直到该进程结束。维护一个非活动状态的连接池几乎不需要什么系统开销。
连接池中的数据库连接
连接池根据唯一的连接字符串被创建。在连接池被创建的同时,连接池将创建最小的数据库连接,当连接不够用时,连接池将逐个添加数据库连接直到达到最大连接数,此后的连接请求将被加入请求队列里。当调用数据库连接对象的Close方法或Dispose方法时,数据库连接将被数据库连接池回收。
当数据库连接使用完成后,要调用Close方法或Dispose方法将它返回连接池。没有显式释放的数据库连接可能会没有返回连接池。
注意不要在类的Finalize方法中调用任何管理类如Connection,DataReader等的Finalize方法,必须将数据库连接的释放权交给连接池。
释放数据库连接
当数据库连接超时或服务已经完成时,连接池将会将其资源释放,这只能通过试图与数据库通讯来判断。如果发现数据库连接不可用,它将被标记为不可用资源。数据库连接池将定时扫描数据库连接,释放所有不可用资源。
如果发现现有的数据库连接不可用,那么可能是该连接被数据库连接池标记为不可用资源了,这时将抛出一个异常。尽管如此,你还是必须释放连接,将它返回连接池。
支持Transaction
数据库连接池内的数据库连接是按照Transaction Context划分的,每当连接池接到连接请求时,他将返回与请求者Transaction Context相匹配的数据库连接。因此,每个连接池都由数个Transaction Context相关的数据库连接和一个Transaction Context无关的数据库连接组成。当数据库连接被返回连接池时,它将被放回对应的Transaction Context组中。
用连接字符串关键字控制数据库连接池
OracleConnection对象的属性ConnectionString有一些能支持连接池控制的key-value字符串。下表是这些key-value字符串的详细说明。
名称
默认值
说明
Connection Lifetime
0
当数据库连接被返回到连接池中时,它的创建时间将与当前时间比较,如果超过了Connection Lifetime规定的时间,它将被释放掉。
为0时将被视为最大连接时间。
Enlist
'true'
当此值为true时,池中现存的所有数据库连接将被加入到它的创建线程的Transaction Context中。如果不存在这个Transaction Context则无任何变化。
Max Pool Size
100
连接池能建立的最大数据库连接数。
Min Pool Size
0
连接池要保持的最小数据库连接数。
Pooling
'true'
当设为true时,数据库连接将由相应的连接池管理。
连接池允许应用程序从连接池中获得一个连接并使用这个连接,而不需要为每一个连接请求重新建立一个连接。一旦一个新的连接被创建并且放置在连接池中,应用程序就可以重复使用这个连接而不必实施整个数据库连接创建过程。 连接池允许应用程序从连接池中获得一个连接并使用这个连接,而不需要为每一个连接请求重新建立一个连接。一旦一个新的连接被创建并且放置在连接池中,应用程序就可以重复使用这个连接而不必实施整个数据库连接创建过程。 当应用程序请求一个连接时,连接池为该应用程序分配一个连接而不是重新建立一个连接;当应用程序使用完连接后,该连接被归还给连接池而不是直接释放。 如何实现连接池 确保你每一次的连接使用相同的连接字符串(和连接池相同);只有连接字符串相同时连接池才会工作。如果连接字符串不相同,应用程序就不会使用连接池而是创建一个新的连接。 优点 使用连接池的最主要的优点是性能。创建一个新的数据库连接所耗费的时间主要取决于网络的速度以及应用程序和数据库服务器的(网络)距离,而且这个过程通常是一个很耗时的过程。而采用数据库连接池后,数据库连接请求可以直接通过连接池满足而不需要为该请求重新连接、认证到数据库服务器,这样就节省了时间。 缺点 数据库连接池中可能存在着多个没有被使用的连接一直连接着数据库(这意味着资源的浪费)。 技巧和提示 1. 当你需要数据库连接时才去创建连接池,而不是提前建立。一旦你使用完连接立即关闭它,不要等到垃圾收集器来处理它。 2. 在关闭数据库连接前确保关闭了所有用户定义的事务。 3. 不要关闭数据库中所有的连接,至少保证连接池中有一个连接可用。如果内存和其他资源是你必须首先考虑的问题,可以关闭所有的连接,然后在下一个请求到来时创建连接池。 连接池FAQ 1. 何时创建连接池? 当第一个连接请求到来时创建连接池;连接池的建立由数据库连接的连接字符创来决定。每一个连接池都与一个不同的连接字符串相关。当一个新的连接请求到来时如果连接字符串和连接池使用的字符串相同,就从连接池取出一个连接;如果不相同,就新建一个连接池。 2. 何时关闭连接池? 当连接池中的所有连接都已经关闭时关闭连接池。 3. 当连接池中的连接都已经用完,而有新的连接请求到来时会发生什么? 当连接池已经达到它的最大连接数目时,有新的连接请求到来时,新的连接请求将放置到连接队列中。当有连接释放给连接池时,连接池将新释放的连接分配给在队列中排队的连接请求。你可以调用close和dispose将连接归还给连接池。 4. 我应该如何允许连接池? 对于.NET应用程序而言,默认为允许连接池。(这意味着你可以不必为这件事情做任何的事情)当然,如果你可以在SQLConnection对象的连接字符串中加进Pooling=true;确保你的应用程序允许连接池的使用。 5. 我应该如何禁止连接池? ADO.NET默认为允许数据库连接池,如果你希望禁止连接池,可以使用如下的方式: 1) 使用SQLConnection对象时,往连接字符串加入如下内容:Pooling=False; 2) 使用OLEDBConnection对象时,往连接字符串加入如下内容:OLE DB Services=-4; ----------------------------------------------------------------------------------------------------------------------------------------------- 连接池 连接池是与业务对象对等的中间层部分,启动一个新的业务对象时,会检查连接池现有的连接,若有就使用它,否则就创建一新连接。包含在ADO.NET中的每个.NET数据提供程序都可实现连接池。 默认连接池是打开的,当关闭连接时,并不是真正关闭实际的数据连接,若超过默认时间(60秒)未再次使用,才会真正被关闭。 若不想存储连接?将下面的字符串添加到OLE DB连接字符串中去:OLE DB Services=-4; 若使用SqlConnection对象,添加下字符:Pooling=False; Connection类 CreateCommand方法可节省一行代码 OleDbCommand cmd=cn.CreateCommand(); 与下面两句等价: OleDbCommand cmd=new OleDbCommand(); cmd.Connection=cn; GetOleDbSchemaTable方法获取数据库架构信息 有两个参数schema(定要返回的架构表)和 restrictions (限制值的 Object 数组)重点是Restrictions参数数组,结构是{“TABLE_CATALOG“,“TABLE_SCHEMA“,“TABLE_NAME“,“COLUMN_NAME“},具体可参考MSDN ----------------------------------------------------------------------------------------------------------------------------------------------- .NET框架组件数据提供程序
图1.访问SQL Server 7.0及以上版本的连接方法
避免自动增加(Auto-Increment)值冲突
作者Blog:
http://blog.csdn.net/qdzx2008/
相关文章
|
|
开链接的排队请求被阻断的时间。下面的示例字符串配置了池的最大和最小容量。
"Server=(local); Integrated Security=SSPI; Database=Northwind;
Max Pool Size=75; Min Pool Size=5"
摘要
连接池允许应用程序从连接池中获得一个连接并使用这个连接,而不需要为每一个连接请求重新建立一个连接。一旦一个新的连接被创建
并且放置在连接池中,应用程序就可以重复使用这个连接而不必实施整个数据库连接创建过程。
当应用程序请求一个连接时,连接池为该应用程序分配一个连接而不是重新建立一个连接;当应用程序使用完连接后,该连接被归还给连接
池而不是直接释放。
如何实现连接池
确保你每一次的连接使用相同的连接字符串(和连接池相同);只有连接字符串相同时连接池才会工作。如果连接字符串不相同,应用程序
就不会使用连接池而是创建一个新的连接。
优点
使用连接池的最主要的优点是性能。创建一个新的数据库连接所耗费的时间主要取决于网络的速度以及应用程序和数据库服务器的
(网络)距离,而且这个过程通常是一个很耗时的过程。而采用数据库连接池后,数据库连接请求可以直接通过连接池满足而不需要为该请
求重新连接、认证到数据库服务器,这样就节省了时间。
缺点
数据库连接池中可能存在着多个没有被使用的连接一直连接着数据库(这意味着资源的浪费)。
技巧和提示
1. 当你需要数据库连接时才去创建连接池,而不是提前建立。一旦你使用完连接立即关闭它,不要等到垃圾收集器来处理它。
2. 在关闭数据库连接前确保关闭了所有用户定义的事务。
3. 不要关闭数据库中所有的连接,至少保证连接池中有一个连接可用。如果内存和其他资源是你必须首先考虑的问题,可以关闭所有的连
接,然后在下一个请求到来时创建连接池。
连接池FAQ
1. 何时创建连接池?
当第一个连接请求到来时创建连接池;连接池的建立由数据库连接的连接字符创来决定。每一个连接池都与一个不同的连接字符串相关。
当一个新的连接请求到来时如果连接字符串和连接池使用的字符串相同,就从连接池取出一个连接;如果不相同,就新建一个连接池。
2. 何时关闭连接池?
当连接池中的所有连接都已经关闭时关闭连接池。
3. 当连接池中的连接都已经用完,而有新的连接请求到来时会发生什么?
当连接池已经达到它的最大连接数目时,有新的连接请求到来时,新的连接请求将放置到连接队列中。当有连接释放给连接池时,连接池将
新释放的连接分配给在队列中排队的连接请求。你可以调用close和dispose将连接归还给连接池。
4. 我应该如何允许连接池?
对于.NET应用程序而言,默认为允许连接池。(这意味着你可以不必为这件事情做任何的事情)当然,如果你可以在SQLConnection对象的连
接字符串中加进Pooling=true;确保你的应用程序允许连接池的使用。
5. 我应该如何禁止连接池?
ADO.NET默认为允许数据库连接池,如果你希望禁止连接池,可以使用如下的方式:
1) 使用SQLConnection对象时,往连接字符串加入如下内容:Pooling=False;
2) 使用OLEDBConnection对象时,往连接字符串加入如下内容:OLE DB Services=-4;
题外话
今天同事问我.Net的数据库连接有没有连接池的概念。我根据脑海里一点模糊的印象回答他.Net是自己实现了连接池,不需要手工再实现一遍。
后来回家确认了一下,原来我的这点印象来自《C#和.Net核心技术》中一小段数据库连接池的介绍。可能当时也只是知道.Net自己实现了不需我们再手工实现,另外就是.Net是通过连接字符串的不同来区分不同的连接的。所以当时就放在一边不管了,呵呵,真是懒惰啊。
这次新问题是,既然.Net底层实现了连接池,当在程序中显式调用close()方法时,连接会不会关掉呢?
《C#和.Net核心技术》中也只有500字不到的一点说明,没有说清楚这点,于是找了MSDN。一切如“众里寻她千百度,蓦然回首,那人却在灯火阑珊处。”一般,终于找到了看明白了弄清楚了。
正文
连接池减少新连接需要打开的次数。池进程保持物理连接的所有权。通过为每个给定的连接配置保留一组活动连接来管理连接。只要用户在连接上调用 Open,池进程就会检查池中是否有可用的连接。如果某个池连接可用,会将该连接返回给调用者,而不是打开新连接。应用程序在该连接上调用 Close 时,池进程会将连接返回到活动连接池集中,而不是真正关闭连接。连接返回到池中之后,即可在下一个 Open 调用中重复使用。
只有配置相同的连接可以建立池连接。ADO.NET 同时保留多个池,每个配置一个池。连接由连接字符串以及 Windows 标识(在使用集成的安全性时)分为多个池。
池连接可以大大提高应用程序的性能和可缩放性。默认情况下,ADO.NET 中启用连接池。除非显式禁用,否则,连接在应用程序中打开和关闭时,池进程将对连接进行优化。还可以提供几个连接字符串修饰符来控制连接池的行为。
池的创建和分配
在初次打开连接时,将根据完全匹配算法创建连接池,该算法将池与连接中的连接字符串关联。每个连接池与不同的连接字符串关联。打开新连接时,如果连接字符串并非与现有池完全匹配,将创建一个新池。按进程、按应用程序域、按连接字符串以及(在使用集成的安全性时)按 Windows 标识来建立池连接。
在以下 C# 示例中创建了三个新的 对象,但是管理时只需要两个连接池。注意,根据为 Initial Catalog
分配的值,第一个和第二个连接字符串有所不同。
" Integrated Security=SSPI;Initial Catalog=Northwind " ))
{
connection.Open();
// Pool A is created.
}
using (SqlConnection connection = new SqlConnection(
" Integrated Security=SSPI;Initial Catalog=pubs " ))
{
connection.Open();
// Pool B is created because the connection strings differ.
}
using (SqlConnection connection = new SqlConnection(
" Integrated Security=SSPI;Initial Catalog=Northwind " ))
{
connection.Open();
// The connection string matches pool A.
}
我们建议您在使用完连接时一定要关闭连接,以便连接可以返回池。要关闭连接,可以使用 Connection 对象的 Close 或 Dispose 方法,也可以通过在 C# 的 using 语句中或在 Visual Basic 的 Using 语句中打开所有连接。不是显式关闭的连接可能不会添加或返回到池中。
摘要:
连接池能在程度上提高数据库访问性能。本文讨论到底何为连接池,它如何提高数据库访问性能,以及如何在.NET中创建连接池并增加或移除连接。
导言
连接数据库是应用程序中耗费大量资源且相对较慢的操作,但它们又是至关紧要的。连接池是已打开的及可重用的数据库连接的一个容器。连接池在所有的数据库连接都关闭时才从内存中释放。使用连接池最基本的好处是提高应用程序的性能及可伸缩性,而其主要缺点是会有一个或多个数据库连接将一直保持打开状态,即使当前不在使用。ADO.NET的Data Providers将默认情况下将使用连接池,如果你不想使用连接池,必须在连接字符串中指定”Polling=false”。连接池中为你提供了空闲的打开的可重用的数据库连接,而不再需要每次在请求数据库数据时新打开一个数据库连接。当数据库连接关闭或释放时,将返回到连接池中保持空闲状态直到新的连接请求到来。如果我们有效地使用连接池,打开和关闭数据库将不再很耗费资源。本文讨论连接池的相关内容以及如何有效的使用连接池来提高应用程序的效率及可伸缩性。
连接池如何工作
连接池中包含打开的可重用的数据库连接。在同一时刻同一应用程序域中可以有多个连接池,但连接池不可以跨应用程序域共享。注意:一个连接池是通过一个唯一的连接字符串来创建。连接池是根据第一次请求数据库连接的连接字符串来创建的,当另外一个不同的连接字符串请求数据库连接时,将创建另一个连接池。因此一个连接字符中对应一个连接池而不是一个数据库对应一个连接池。如以下代码所示
代码1
// 新建一个连接池
SqlConnection sqlConnection = new SqlConnection();
sqlConnection.ConnectionString =
"Server=localhost;Database=test;User ID=joydip;Password=joydip;Trusted_Connection=False";
sqlConnection.Open();
代码2
// 因为连接字符串不同,新建另一个连接池
SqlConnection conn = new SqlConnection();
sqlConnection.ConnectionString =
"Server=localhost;Database=test;User ID=test;Password=test;Trusted_Connection=False";
sqlConnection.Open();
代码3
// 因为连接字符串与代码1相同,不再创建连接池.
SqlConnection conn = new SqlConnection();
sqlConnection.ConnectionString =
"Server=localhost;Database=test;User ID=joydip;Password=joydip;Trusted_Connection=False";
sqlConnection.Open();
当有新的数据库连接请求到来时,连接池中连接进行了响应而不用创建一个新的数据库连接,也就是说数据库连接可以被重用,而不需要重新新建连接。因此这提高了应用程序的效率和可伸缩性。当你在应用程序中关闭一个打开的数据库连接时,该连接返回到连接池中等待重新连接直到等待超时。在这个时间内等待同一数据库相同连接信息的连接请求。如果这个时间内没有连接请求,这个数据库连接将被关闭,并从连接池中移除这个连接实例。
当一个新的连接池创建后,数据库连接被添加到池中,连接池和池中的连接立即可被使用。连接池中将填满连接字个串中指定的最小连接数量的连接。连接池中连接在长时间不活动或超出指定的生存期时将被移除。
连接池由连接池管理器维护。当后续的连接请求到来,连接池管理器在连接池中寻找可用的空闲的连接,如果存在就交给应用程序使用。以下描述了当一个新的连接请求到来时连接管理器如何工作
· 如果有未用连接可用,返回该连接
· 如果池中连接都已用完,创建一个新连接添加到池中
· 如果池中连接已达到最大连接数,请求进入等待队列直到有空闲连接可用
通过连接字符串中传递的参数可以控制连接池。基本的参数包括:
· Connect Timeout
· Min Pool Size
· Max Pool Siz
· Pooling
为了有效的使用连接池,记住数据库操作完成后马上关闭连接,这样连接才能返回连接池中。
提高连接池性能
我们应该在最晚时刻打开连接并在最早时刻释放连接,即在使用完成后立即释放。数据库连接应该在真正请求数据时才打开,而不应在使用之前就请求连接,这会减少池中可用连接的数量,因此有害于连接池的操作及应用程序性能。数据库连接应使用完成后立即释放,这能促进连接池更好的使用,因为连接可以返回池中被重新使用。以下代码展示如何在应用程序中有效地打开和关闭连接
代码4
SqlConnection sqlConnection = new SqlConnection(connectionString);
try
{
sqlConnection.Open();
//Some Code
}
finally
{
sqlConnection.Close();
}
代码4可以使用”using”关键字进一步简化如以下代码所示
代码5
using(SqlConnection sqlConnection = new SqlConnection(connectionString))
{
sqlConnection.Open();
//Some Code
}
注:以上代码5中的”using”关键字将隐含地生成try-finally块
以下列举了更好地使用连接池的几个可参考要点
· 在需要使用时才打开连接,并在完成操作后马上关闭
· 在关闭连接时先关闭相关用户定义的事务
· 确保维持连接池中至少有一个打开的连接
· 在使用集成身份验证的情况下避免使用连接池
连接池可以通过以下途径进行监控
· 使用sp_who或sp_who2存储过程
· 使用SQL Server的Profiler
· 使用性能监视器的性能计数器
参考文献
Tuning Up ADO.NET Connection Pooling in ASP.NET Applications
Connection Pooling for the .NET Framework Data Provider for SQL Server
The .NET Connection Pool Lifeguard
ADO.NET Connection Pooling Explained
结语
连接池是数据库连接对象的容器,只要其中存在活动的或打开的连接它维持活动状态。当使用一个连接字符串来请求数据库连接时,将分配一个新的连接池。通过在应用程序中使用相同的连接字符串我们可以提高应用程序的性能和可伸缩性。然而如果我们不正确地使用连接池可能给我们的应用程序带来负效果。MSDN中说“连接是提高应用程序性能的有力工具,但如果使用不当连接池非但不是有益的而且是害的”。本文讨论了连接池的相关内容以及如何有效的使用连接池来提高应用程序的效率及可伸缩性。
摘录自MSDN:
建立池连接可以显著提高应用程序的性能和可缩放性。SQL Server .NET Framework 数据提供程序自动为 ADO.NET 客户端应用程序提供连接池。您也可以提供几个连接字符串修饰符来控制连接池行为,请参见本主题内下文中“使用连接字符串关键字控制连接池”这一节。
池的创建和分配
当连接打开时,将根据一种精确的匹配算法来创建连接池,该算法会使连接池与连接中的字符串相关联。每个连接池都与一个不同的连接字符串相关联。当新连接打开时,如果连接字符串不精确匹配现有池,则将创建一个新池。
在以下示例中,将创建三个新的 SqlConnection 对象,但只需要使用两个连接池来管理这些对象。请注意,第一个和第二个连接字符串的差异在于为 Initial Catalog
分配的值。
SqlConnection conn = new SqlConnection(); conn.ConnectionString = "Integrated Security=SSPI;Initial Catalog=northwind"; conn.Open(); // Pool A is created. SqlConnection conn = new SqlConnection(); conn.ConnectionString = "Integrated Security=SSPI;Initial Catalog=pubs"; conn.Open(); // Pool B is created because the connection strings differ. SqlConnection conn = new SqlConnection(); conn.ConnectionString = "Integrated Security=SSPI;Initial Catalog=northwind"; conn.Open(); // The connection string matches pool A.
连接池一旦创建,直到活动进程终止时才会被毁坏。非活动或空池的维护只需要最少的系统开销。
连接的添加
连接池是为每个唯一的连接字符串创建的。当创建一个池后,将创建多个连接对象并将其添加到该池中,以满足最小池大小的要求。连接将根据需要添加到池中,直至达到最大池大小。
当请求 SqlConnection 对象时,如果存在可用的连接,则将从池中获取该对象。若要成为可用连接,该连接当前必须未被使用,具有匹配的事务上下文或者不与任何事务上下文相关联,并且具有与服务器的有效链接。
如果已达到最大池大小且不存在可用的连接,则该请求将会排队。当连接被释放回池中时,连接池管理程序通过重新分配连接来满足这些请求。对 Connection 调用 Close 或 Dispose 时,连接被释放回池中。
警告 建议使用完 Connection 后始终将其关闭,以便连接可以返回到池中。这可以使用 Connection 对象的 Close 或 Dispose 方法来实现。不是显式关闭的连接可能不会添加或返回到池中。例如,如果连接已超出范围但没有显式关闭,则仅当达到最大池大小而该连接仍然有效时,该连接才会返回到连接池中。
注意 不要在类的 Finalize 方法中对 Connection、 DataReader 或任何其他托管对象调用 Close 或 Dispose。在终结器中,仅释放类直接拥有的非托管资源。如果类不拥有任何非托管资源,则不要在类定义中包含 Finalize 方法。有关更多信息,请参见 垃圾回收编程。
连接的移除
如果连接生存期已过期,或者连接池管理程序检测到与服务器的连接已断开,连接池管理程序将从池中移除该连接。请注意,只有在尝试与服务器进行通信后,才可以检测到这种情况。如果发现某连接不再连接到服务器,则会将其标记为无效。连接池管理程序会定期扫描连接池,查找已释放到池中并标记为无效的对象。找到后,这些连接将被永久移除。
如果存在与已消失的服务器的连接,那么即使连接池管理程序未检测到已断开的连接并将其标记为无效,仍有可能将此连接从池中取出。当发生这种情况时,将生成异常。但是,为了将该连接释放回池中,仍必须将其关闭。
事务支持
连接是根据事务上下文来从池中取出并进行分配的。请求线程和所分配的连接的上下文必须匹配。因此,每个连接池实际上又分为不具有关联事务上下文的连接以及 N 个各自包含与一个特定事务上下文的连接的子部分。
当连接关闭时,它将被释放回池中,并根据其事务上下文放入相应的子部分。因此,即使分布式事务仍然挂起,仍可以关闭该连接而不会生成错误。这样,您就可以在随后提交或中止分布式事务。
使用连接字符串关键字控制连接池
SqlConnection 对象的 ConnectionString 属性支持连接字符串键/值对,这些键/值对可用于调整连接池逻辑的行为。
下表描述了可用于调整连接池行为的 ConnectionString 值。
名称 | 默认值 | 说明 |
---|---|---|
Connection Lifetime | 0 | 当连接返回到池中时,将对它的创建时间和当前时间进行比较,如果时间间隔超过由 Connection Lifetime 指定的值(以秒为单位),则会毁坏该连接。在聚集配置中可以使用它来强制在运行服务器和刚联机的服务器之间达到负载平衡。 如果值为零 (0),则将使池连接具有最大的超时期限。 |
Connection Reset | 'true' | 确定在从池中移除数据库连接时是否将其重置。对于 Microsoft SQL Server 版本 7.0,如果设置为 false,将避免在获取连接时经历一个额外的往返过程,但必须注意的是连接状态(如数据库上下文)不会被重置。 |
Enlist | 'true' | 当为 true 时,如果存在事务上下文,池管理程序将自动在创建线程的当前事务上下文中登记连接。 |
Max Pool Size | 100 | 池中允许的最大连接数。 |
Min Pool Size | 0 | 池中维护的最小连接数。 |
Pooling | 'true' | 当为 true 时,将从相应的池中取出连接,或者在必要时创建连接并将其添加到相应的池中。 |
连接池的性能计数器
SQL Server .NET Framework 数据提供程序添加了几个性能计数器,它们将使您能够微调连接池特性,检测与失败的连接尝试相关的间歇性问题,并检测与对 SQL Server 的超时请求相关的问题。
下表列出了可以在“.NET CLR 数据”性能对象下的“性能监视器”中访问的连接池计数器。
计数器 | 说明 |
---|---|
SqlClient: Current # pooled and non pooled connections | 当前池连接或非池连接的数目。 |
SqlClient: Current # pooled connections | 当前所有池中与特定进程关联的连接的数目。 |
SqlClient: Current # connection pools | 当前与特定进程关联的池的数目。 |
SqlClient: Peak # pooled connections | 自特定进程开始以来所有池中的连接数峰值。请注意:此计数器只有在与特定进程实例关联时才可用。_Global 实例始终返回 0。 |
SqlClient: Total # failed connects | 打开连接的尝试因任何原因而失败的总次数。 |
注意 将 SQL Server .NET Framework 数据提供程序性能计数器与 ASP.NET 应用程序一起使用时,只有 _Global 实例是可用的。因此,性能计数器返回的值是所有 ASP.NET 应用程序的计数器值的总和。
ADO.NET 的数据存取性能
级别: 初级
Ramesh Theivendran, 架构师
2004 年 8 月 01 日
本文介绍了使用 ADO.NET 开发数据库应用程序时应考虑的一些基本的数据访问性能问题。
数据访问在商业应用程序中扮演着关键角色。性能在任何数据密集型的应用程序中都是应该考虑的关键因素。有很多因素能够对数据访问性能产生负面影响,像网络负载、数据库服务器负载、未优化的 SQL 语句,等等。除此以外,还有一些其他因素要考虑,包括大多数应用程序执行的各种数据访问操作,比如打开和关闭连接、获取结果集、blob 访问以及元数据检索。在本文中,我将分析一些数据访问操作和提出一些提高数据库访问性能的建议。
在本文中,我将使用 Borland® C#Builder™ 附带的 Borland Data Provider (BDP) for .NET 和 Borland® Delphi™ 8 for the Microsoft® .NET Framework (简写为“Delphi 8 for .NET”),以及 IBM® DB2® 数据提供者来访问 IBM® DB2® Universal Database™ (UDB)。
![]() ![]() |
![]()
|
建立新的数据库连接有时代价非常昂贵,因为它涉及到分配客户机和服务器资源、授权用户,以及其他的验证。通过建立连接和在随后请求中重用同一连接,能够显著提高应用程序的性能。当客户机在本地处理数据时,数据库连接不一定是活动的,所以单个连接有可能被多个客户机共享访问。因此,连接池(也就是数据库连接的缓存)能够提高应用的性能和可伸缩性,尤其是在多层体系结构中。
在 ADO.NET 中,连接池通过唯一的连接字符串来标识。当新连接打开时,如果连接字符串没有精确匹配任何现有的池,则创建新的连接池。新连接池创建之后,则创建最小数量的连接对象,并添加到后台的池中。如果池中所有已存在的连接都是忙碌的,那么新的连接被添加到池中,直到达到池的最大尺寸。默认情况下,连接池参数的默认值可以用连接字符串覆盖,比如 Min Pool Size 和 Max Pool Size。
池中的连接分为不带事务上下文的连接和带详细事务上下文的连接。当打开一个 ADO.NET 连接时,根据事务上下文从池中取得连接。如果连接还未关联事务,那么它将从非事务连接池中取得。
关闭连接操作会将占用的连接返回给连接池,以便于重用。池中的连接与生命周期相关联,连接池管理器定期扫描无用和过期的连接,并从池中删除掉。一旦创建完成,连接池在整个生命周期过程中将保持活动状态。
为了展示连接池实际提供的性能收益,我将编写一个简单的 .NET 远程管理应用程序。有关.NET 远程管理的一些基本知识,您可以参阅我以前的文章 在 .NET 中使用 BDP 和 DB2 构建分布式数据库应用程序。
远程服务器公开了两个方法,GetDataBDP() 和 GetDataDB2() ,通过这两个方法,可以分别使用 Borland Data Provider (BDP) (Borland.Data.Provider) 和 IBM DB2 Data Provider (IBM.Data.DB2) 来填充和返回数据集。对于来自客户机的每一请求,都会打开连接,并在处理完 SQL 请求之后关闭连接。GetDataDB2() 利用一个标志来决定是否启用连接池。当前版本的 BDP 不支持连接池。
下面是一些基本的测试结果,显示了按分钟计算所占用的时间。这些结果不应作为基准来考虑。但是,您可以看到,随着更多的请求到达服务器,如果在中间层没有连接池的话,应用程序的性能将会变差。
请求数: | 250 个请求 带连接池 | 250 个请求 不带连接池 | 500 个请求 带连接池 | 500 个请求 不带连接池 | |
数据提供者: | IBM DB2 | 00:17.5468750 | 02:01.4531250 | 00:32.8750000 | 04:03.5468750 |
BDP - DB2 | N/A | 02:01.1406250 | N/A | 04:01.6718750 |
下面是用于服务器和客户机的两段代码。请参考完整的源代码列表。
RemoteServer.cs
public class RemoteDataProvider : |
RemoteClient.cs
public class RemotingClient { public static void Main() { TestPooling(); } private static void TestPooling() { IRemoteDataService remDS = null; ArrayList stat = new ArrayList(); HttpChannel channel = new HttpChannel(); ChannelServices.RegisterChannel(channel); String ClientID = Guid.NewGuid().ToString(); try { remDS = |
![]() ![]() |
![]()
|
单向、只读游标提供更好的吞吐量,还使用了更少的客户机和服务器资源。使用单向游标的话,在数据访问层无需任何缓存,并且无需维护与服务器中记录相关的当前记录位置。数据作为流来读取,而记录一个接一个地处理。单向结果集对于报表、数据处理应用程序来说是很理想的,因为这些应用程序在获取数据时执行同样的操作。
在 ADO.NET 中,DataReader 返回单向的结果集。DataAdapter 扮演的角色是数据库和数据集之间的管道,使用 DataReader 从数据库中提取记录并填入数据集。数据集缓存数据,同时起到了一个 in-memory 关系数据库的作用。
因此,视应用程序的需要,您可以直接使用 DataReader 每次处理一条记录,或者使用 DataAdapter 来填充数据集,这样可以提供记录的完整集合,并在稍后分析数据集在客户机上的更改,再保存回数据库。不管哪种情况,选择 SQL 语句对于更好的吞吐量和整体性能来说都是非常重要的。
![]() ![]() |
![]()
|
Blob 数据最大可达 4 GB。由于海量数据可能通过线路传输,因此最好不要同时提取 blob 数据及其他标量数据。使用 blob 数据时,很重要的一点是要理解数据库客户机库中的底层访问机制。大多数数据库客户机提供了不止一种访问 blob 数据类型的方法。根据 blob 数据类型的不同,客户机可以绑定巨大的缓冲区或者使用 blob 定位器来获取 blob 数据。
在绑定每次提取的巨大缓冲区时,可用的 blob 数据,或者高达最大缓存大小的 blob 数据,均传输到客户机。而另一方面,blob 定位器基本上是引用数据库服务器上的 blob 数据。在最初提取数据期间,只有定位器被传输到客户机。一旦客户机获得了 blob 定位器,它稍后会调用 blob 访问方法,以便读取和写入 blob 数据。
因此,要改进应用程序处理 blob 数据的性能,必须注意分别提取 blob 数据,或采用新的 SQL 请求,或使用定位器。同时,由于不一定会处理 blob 数据,只有在必要时或是应用程序显式请求时才提取它们。
![]() ![]() |
![]()
|
元数据检索是另一种昂贵的操作(因为它可能涉及到连接几个系统表,检索特定数据库对象的元数据),在运行时应该尽量减少或者完全消除。大多数数据库对象的元数据检索可以在设计时完成,而模式信息可以持久存储为 XML 或者任何特定于应用程序的格式。
运行时元数据无法完全消除。在一些要分析关系数据或者对象持久性的复杂应用程序中,,可能需要发现运行时数据库对象的特征。在这些情况下,必须调整访问系统表的 SQL 语句。
在当前版本的 ADO.NET 中,元数据检索功能还无法足以检索有关数据库对象的所信息。DataReader 和 DataAdapter 分别有 GetSchemaTable 和 FillSchema 方法,用于提取当前 SQL 请求的提供者元数据。BDP 扩展了 ADO.NET,并提供了检索各种数据库对象元数据的功能。
下面的测试结果显示了 BDP 和 IBM DB2 数据提供者在大多数基本数据访问操作上执行得同样好。然而,我的确注意到,如果使用 CHAR 数据类型来取代 VARCHAR 数据类型,IBM DB2 数据提供者看来要对数据进行空白填充(blank-pad),这导致了性能下降。
数据访问 | 使用 DataReader 提取 10,000 条记录 | 利用 GetSchemaTable 提取 10,000 条记录 | 利用 6K BLOB 数据 提取 100 条记录 | |
数据提供者: | BDP - DB2 | 00:51.7243760 | 00:52.1049232 | 1:46:2527840 |
IBM DB2 | 00:51.7444048 | 00:51.9246640 | 1:38.2012064 |
![]() ![]() |
![]()
|
数据库客户机库允许客户机绑定单个缓冲区和每次提取一条记录。每次提取需要一次网络往返,这在应用程序处理海量结果集时会影响性能。虽然不推荐对海量结果集进行检索,但这是无法避免的,特别是在类似于 OLAP 或收集历史数据统计信息这样的应用程序场景中。一些数据库客户机库允许读取记录块,客户机会绑定缓冲区的数组,并在单次往返中检索记录块。
在任何非连接的数据访问模型中(比如 Borland DataSnap),当 ADO.NET 将所有客户机更改持久存储回数据库时,需要为每一修改的记录执行一条 SQL 语句。例如,如果有 n条插入记录的话,不是执行 n次相同的 INSERT 语句,客户机可以传递一组参数缓冲区,以便执行批量插入。块读写能够显著改进性能,特别是在 WAN 环境中,因为记录可以在单次网络往返中以批量形式接收和发送。BDP 当前不支持块读写。
![]() ![]() |
![]()
|
长时间运行的查询,比如复杂连接或涉及整个表扫描的查询,会对应用程序的响应能力产生负面影响。当数据库正在处理 SQL 请求时,如果 SQL 请求未阻塞的话,客户机可以处理本地应用程序内部事务。如果异步执行不可用, SQL 请求可以在而主线程继续运行的情况下,通过单独的线程进行。
目前,ADO.NET 框架不支持异步执行模式,但未来版本可能会支持。
![]() ![]() |
![]()
|
如果各种优化因素未考虑周到的话,数据访问可能会成为主要瓶颈。除了调优数据库和调优 SQL 使之具有更佳的选择性(selectivity)之外,其他度量因素(比如连接池、运行时最小化元数据检索、移除长期运行的查询以分开线程、只有在必要时才提取 blob)也可以优化数据访问性能,从而为任何数据密集型应用程序提供更好的响应能力。因此,根据应用程序的需要,选择合适的数据访问操作可以提高性能和可伸缩性。
![]() ![]() |
![]()
|
名字 | 大小 | 下载方法 |
---|---|---|
source.zip | 35.5 KB | HTTP |
![]() | ||||
![]() | 关于下载方法的信息 | ![]() |
![]() | ||
![]() | Ramesh Theivendran从 1995 年开始就是 Borland RAD database connectivity R&D 小组成员。目前,他正在他们的 Win32 和 .NET 产品小组中致力于数据库连接性研究,并担任 dbExpress 和 Borland Data Provider (BDP) for .NET 的架构师。 |