(转载)ADO.NET 的数据存取性能
2006-04-18 09:55
375 查看
本文介绍了使用ADO.NET开发数据库应用程序时应考虑的一些基本的数据访问性能问题。
简介
数据访问在商业应用程序中扮演着关键角色。性能在任何数据密集型的应用程序中都是应该考虑的关键因素。有很多因素能够对数据访问性能产生负面影响,像网络负载、数据库服务器负载、未优化的SQL语句,等等。除此以外,还有一些其他因素要考虑,包括大多数应用程序执行的各种数据访问操作,比如打开和关闭连接、获取结果集、blob访问以及元数据检索。在本文中,我将分析一些数据访问操作和提出一些提高数据库访问性能的建议。
在本文中,我将使用Borland®C#Builder™附带的BorlandDataProvider(BDP)for.NET和Borland®Delphi™8fortheMicrosoft®.NETFramework(简写为“Delphi8for.NET”),以及IBM®DB2®数据提供者来访问IBM®DB2®UniversalDatabase™(UDB)。
连接池
建立新的数据库连接有时代价非常昂贵,因为它涉及到分配客户机和服务器资源、授权用户,以及其他的验证。通过建立连接和在随后请求中重用同一连接,能够显著提高应用程序的性能。当客户机在本地处理数据时,数据库连接不一定是活动的,所以单个连接有可能被多个客户机共享访问。因此,连接池(也就是数据库连接的缓存)能够提高应用的性能和可伸缩性,尤其是在多层体系结构中。
在ADO.NET中,连接池通过唯一的连接字符串来标识。当新连接打开时,如果连接字符串没有精确匹配任何现有的池,则创建新的连接池。新连接池创建之后,则创建最小数量的连接对象,并添加到后台的池中。如果池中所有已存在的连接都是忙碌的,那么新的连接被添加到池中,直到达到池的最大尺寸。默认情况下,连接池参数的默认值可以用连接字符串覆盖,比如MinPoolSize和MaxPoolSize。
池中的连接分为不带事务上下文的连接和带详细事务上下文的连接。当打开一个ADO.NET连接时,根据事务上下文从池中取得连接。如果连接还未关联事务,那么它将从非事务连接池中取得。
关闭连接操作会将占用的连接返回给连接池,以便于重用。池中的连接与生命周期相关联,连接池管理器定期扫描无用和过期的连接,并从池中删除掉。一旦创建完成,连接池在整个生命周期过程中将保持活动状态。
为了展示连接池实际提供的性能收益,我将编写一个简单的.NET远程管理应用程序。有关.NET远程管理的一些基本知识,您可以参阅我以前的文章在.NET中使用BDP和DB2构建分布式数据库应用程序。
远程服务器公开了两个方法,GetDataBDP()和GetDataDB2(),通过这两个方法,可以分别使用BorlandDataProvider(BDP)(Borland.Data.Provider)和IBMDB2DataProvider(IBM.Data.DB2)来填充和返回数据集。对于来自客户机的每一请求,都会打开连接,并在处理完SQL请求之后关闭连接。GetDataDB2()利用一个标志来决定是否启用连接池。当前版本的BDP不支持连接池。
下面是一些基本的测试结果,显示了按分钟计算所占用的时间。这些结果不应作为基准来考虑。但是,您可以看到,随着更多的请求到达服务器,如果在中间层没有连接池的话,应用程序的性能将会变差。
下面是用于服务器和客户机的两段代码。请参考完整的源代码列表。
RemoteServer.cs
RemoteClient.cs
单向(forwardonly)游标
单向、只读游标提供更好的吞吐量,还使用了更少的客户机和服务器资源。使用单向游标的话,在数据访问层无需任何缓存,并且无需维护与服务器中记录相关的当前记录位置。数据作为流来读取,而记录一个接一个地处理。单向结果集对于报表、数据处理应用程序来说是很理想的,因为这些应用程序在获取数据时执行同样的操作。
在ADO.NET中,DataReader返回单向的结果集。DataAdapter扮演的角色是数据库和数据集之间的管道,使用DataReader从数据库中提取记录并填入数据集。数据集缓存数据,同时起到了一个in-memory关系数据库的作用。
因此,视应用程序的需要,您可以直接使用DataReader每次处理一条记录,或者使用DataAdapter来填充数据集,这样可以提供记录的完整集合,并在稍后分析数据集在客户机上的更改,再保存回数据库。不管哪种情况,选择SQL语句对于更好的吞吐量和整体性能来说都是非常重要的。
Blob访问
Blob数据最大可达4GB。由于海量数据可能通过线路传输,因此最好不要同时提取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和IBMDB2数据提供者在大多数基本数据访问操作上执行得同样好。然而,我的确注意到,如果使用CHAR数据类型来取代VARCHAR数据类型,IBMDB2数据提供者看来要对数据进行空白填充(blank-pad),这导致了性能下降。
读写数据块
数据库客户机库允许客户机绑定单个缓冲区和每次提取一条记录。每次提取需要一次网络往返,这在应用程序处理海量结果集时会影响性能。虽然不推荐对海量结果集进行检索,但这是无法避免的,特别是在类似于OLAP或收集历史数据统计信息这样的应用程序场景中。一些数据库客户机库允许读取记录块,客户机会绑定缓冲区的数组,并在单次往返中检索记录块。
在任何非连接的数据访问模型中(比如BorlandDataSnap),当ADO.NET将所有客户机更改持久存储回数据库时,需要为每一修改的记录执行一条SQL语句。例如,如果有[i]n条插入记录的话,不是执行n次相同的INSERT语句,客户机可以传递一组参数缓冲区,以便执行批量插入。块读写能够显著改进性能,特别是在WAN环境中,因为记录可以在单次网络往返中以批量形式接收和发送。BDP当前不支持块读写。
异步执行
长时间运行的查询,比如复杂连接或涉及整个表扫描的查询,会对应用程序的响应能力产生负面影响。当数据库正在处理SQL请求时,如果SQL请求未阻塞的话,客户机可以处理本地应用程序内部事务。如果异步执行不可用,SQL请求可以在而主线程继续运行的情况下,通过单独的线程进行。
目前,ADO.NET框架不支持异步执行模式,但未来版本可能会支持。
结束语
如果各种优化因素未考虑周到的话,数据访问可能会成为主要瓶颈。除了调优数据库和调优SQL使之具有更佳的选择性(selectivity)之外,其他度量因素(比如连接池、运行时最小化元数据检索、移除长期运行的查询以分开线程、只有在必要时才提取blob)也可以优化数据访问性能,从而为任何数据密集型应用程序提供更好的响应能力。因此,根据应用程序的需要,选择合适的数据访问操作可以提高性能和可伸缩性。
下载
简介
数据访问在商业应用程序中扮演着关键角色。性能在任何数据密集型的应用程序中都是应该考虑的关键因素。有很多因素能够对数据访问性能产生负面影响,像网络负载、数据库服务器负载、未优化的SQL语句,等等。除此以外,还有一些其他因素要考虑,包括大多数应用程序执行的各种数据访问操作,比如打开和关闭连接、获取结果集、blob访问以及元数据检索。在本文中,我将分析一些数据访问操作和提出一些提高数据库访问性能的建议。
在本文中,我将使用Borland®C#Builder™附带的BorlandDataProvider(BDP)for.NET和Borland®Delphi™8fortheMicrosoft®.NETFramework(简写为“Delphi8for.NET”),以及IBM®DB2®数据提供者来访问IBM®DB2®UniversalDatabase™(UDB)。
连接池
建立新的数据库连接有时代价非常昂贵,因为它涉及到分配客户机和服务器资源、授权用户,以及其他的验证。通过建立连接和在随后请求中重用同一连接,能够显著提高应用程序的性能。当客户机在本地处理数据时,数据库连接不一定是活动的,所以单个连接有可能被多个客户机共享访问。因此,连接池(也就是数据库连接的缓存)能够提高应用的性能和可伸缩性,尤其是在多层体系结构中。
在ADO.NET中,连接池通过唯一的连接字符串来标识。当新连接打开时,如果连接字符串没有精确匹配任何现有的池,则创建新的连接池。新连接池创建之后,则创建最小数量的连接对象,并添加到后台的池中。如果池中所有已存在的连接都是忙碌的,那么新的连接被添加到池中,直到达到池的最大尺寸。默认情况下,连接池参数的默认值可以用连接字符串覆盖,比如MinPoolSize和MaxPoolSize。
池中的连接分为不带事务上下文的连接和带详细事务上下文的连接。当打开一个ADO.NET连接时,根据事务上下文从池中取得连接。如果连接还未关联事务,那么它将从非事务连接池中取得。
关闭连接操作会将占用的连接返回给连接池,以便于重用。池中的连接与生命周期相关联,连接池管理器定期扫描无用和过期的连接,并从池中删除掉。一旦创建完成,连接池在整个生命周期过程中将保持活动状态。
为了展示连接池实际提供的性能收益,我将编写一个简单的.NET远程管理应用程序。有关.NET远程管理的一些基本知识,您可以参阅我以前的文章
远程服务器公开了两个方法,GetDataBDP()和GetDataDB2(),通过这两个方法,可以分别使用BorlandDataProvider(BDP)(Borland.Data.Provider)和IBMDB2DataProvider(IBM.Data.DB2)来填充和返回数据集。对于来自客户机的每一请求,都会打开连接,并在处理完SQL请求之后关闭连接。GetDataDB2()利用一个标志来决定是否启用连接池。当前版本的BDP不支持连接池。
下面是一些基本的测试结果,显示了按分钟计算所占用的时间。这些结果不应作为基准来考虑。但是,您可以看到,随着更多的请求到达服务器,如果在中间层没有连接池的话,应用程序的性能将会变差。
请求数: | 250个请求 带连接池 | 250个请求 不带连接池 | 500个请求 带连接池 | 500个请求 不带连接池 | |
数据提供者: | IBMDB2 | 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
publicclassRemoteDataProvider: MarshalByRefObject,IRemoteDataProvider { publicDataSetGetDataBDP() { DataSetds=null; StringconnString= "Provider=DB2;Assembly=Borland.Data.Db2,Version=1. 5.1.0,Culture=neutral,PublicKeyToken=91d62ebb5b0d1 b1b;Database=toolsdb;UserName=myuser;Password=mypasswd"; try { ds=newDataSet(); BdpConnectionConn=newBdpConnection(connString); Conn.Open(); BdpDataAdapteradapter=newBdpDataAdapter(m_commText,Conn); Console.WriteLine("SQLtoDB2:"+m_commText); adapter.Fill(ds,"Table1"); Conn.Close(); } catch(Exceptione) { throwe; } returnds; } publicDataSetGetDataDB2(boolbPool) { DataSetds=null; StringconnString="Database=toolsdb;UID=myuser;PWD=mypasswd;"; if(bPool) { Console.WriteLine("ConnectionPoolingON..."); connString=connString+"pooling=true;Minpoolsize=100"; } else { Console.WriteLine("ConnectionPoolingOFF..."); connString=connString+"pooling=false"; } try { ds=newDataSet(); DB2ConnectionConn=newDB2Connection(connString); Conn.Open(); DB2DataAdapteradapter=newDB2DataAdapter(m_commText,Conn); Console.WriteLine("SQLtoDB2:"+m_commText); adapter.Fill(ds,"Table1"); Conn.Close(); } catch(Exceptione) { throwe; } returnds; } } |
publicclassRemotingClient { publicstaticvoidMain() { TestPooling(); } privatestaticvoidTestPooling() { IRemoteDataServiceremDS=null; ArrayListstat=newArrayList(); HttpChannelchannel=newHttpChannel(); ChannelServices.RegisterChannel(channel); StringClientID=Guid.NewGuid().ToString(); try { remDS= (IRemoteDataService)Activator.GetObject(typeof(IRemoteDataService), "http://testserver:8000/RemoteDataService.soap"); if(remDS!=null) { stat.Add(GetData(remDS,250,false,true)); stat.Add(GetData(remDS,250,false,false)); stat.Add(GetData(remDS,250,true,false)); } Console.WriteLine(); for(inti=0;i<stat.Count;i++) { Console.WriteLine((String)stat); } } catch(Exceptione) { Console.WriteLine(e.Message); } } privatestaticString GetData(IRemoteDataServiceremDS,intnoofRequest,boolbBDP,boolbPool) { IRemoteDataProviderremDP=null; DataSetds=null; StringOut=""; DateTimestime=DateTime.Now; StringClientID=Guid.NewGuid().ToString(); for(inti=0;i<noofRequest;i++) { remDP=remDS.GetDataProvider(ClientID); remDP.CommandText="SELECT*FROMADDRESSBOOK"; if(bBDP) { ds=remDP.GetDataBDP(); } else { if(bPool) ds=remDP.GetDataDB2(true); else ds=remDP.GetDataDB2(false); } if(ds!=null) { Console.WriteLine("Datareceivedfromtheremoteserver"); Utils.PrintData(ds); } } TimeSpants=DateTime.Now-stime; if(bBDP) { Out="TimedurationwithoutPooling(BDP)="+ts.ToString(); } else { if(bPool) Out="TimedurationwithPooling(DB2)="+ts.ToString(); else Out="TimedurationwithoutPooling(DB2)="+ts.ToString(); } returnOut; } |
单向、只读游标提供更好的吞吐量,还使用了更少的客户机和服务器资源。使用单向游标的话,在数据访问层无需任何缓存,并且无需维护与服务器中记录相关的当前记录位置。数据作为流来读取,而记录一个接一个地处理。单向结果集对于报表、数据处理应用程序来说是很理想的,因为这些应用程序在获取数据时执行同样的操作。
在ADO.NET中,DataReader返回单向的结果集。DataAdapter扮演的角色是数据库和数据集之间的管道,使用DataReader从数据库中提取记录并填入数据集。数据集缓存数据,同时起到了一个in-memory关系数据库的作用。
因此,视应用程序的需要,您可以直接使用DataReader每次处理一条记录,或者使用DataAdapter来填充数据集,这样可以提供记录的完整集合,并在稍后分析数据集在客户机上的更改,再保存回数据库。不管哪种情况,选择SQL语句对于更好的吞吐量和整体性能来说都是非常重要的。
Blob访问
Blob数据最大可达4GB。由于海量数据可能通过线路传输,因此最好不要同时提取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和IBMDB2数据提供者在大多数基本数据访问操作上执行得同样好。然而,我的确注意到,如果使用CHAR数据类型来取代VARCHAR数据类型,IBMDB2数据提供者看来要对数据进行空白填充(blank-pad),这导致了性能下降。
数据访问 | 使用DataReader 提取10,000条记录 | 利用GetSchemaTable 提取10,000条记录 | 利用6KBLOB数据 提取100条记录 | |
数据提供者: | BDP-DB2 | 00:51.7243760 | 00:52.1049232 | 1:46:2527840 |
IBMDB2 | 00:51.7444048 | 00:51.9246640 | 1:38.2012064 |
数据库客户机库允许客户机绑定单个缓冲区和每次提取一条记录。每次提取需要一次网络往返,这在应用程序处理海量结果集时会影响性能。虽然不推荐对海量结果集进行检索,但这是无法避免的,特别是在类似于OLAP或收集历史数据统计信息这样的应用程序场景中。一些数据库客户机库允许读取记录块,客户机会绑定缓冲区的数组,并在单次往返中检索记录块。
在任何非连接的数据访问模型中(比如BorlandDataSnap),当ADO.NET将所有客户机更改持久存储回数据库时,需要为每一修改的记录执行一条SQL语句。例如,如果有[i]n条插入记录的话,不是执行n次相同的INSERT语句,客户机可以传递一组参数缓冲区,以便执行批量插入。块读写能够显著改进性能,特别是在WAN环境中,因为记录可以在单次网络往返中以批量形式接收和发送。BDP当前不支持块读写。
异步执行
长时间运行的查询,比如复杂连接或涉及整个表扫描的查询,会对应用程序的响应能力产生负面影响。当数据库正在处理SQL请求时,如果SQL请求未阻塞的话,客户机可以处理本地应用程序内部事务。如果异步执行不可用,SQL请求可以在而主线程继续运行的情况下,通过单独的线程进行。
目前,ADO.NET框架不支持异步执行模式,但未来版本可能会支持。
结束语
如果各种优化因素未考虑周到的话,数据访问可能会成为主要瓶颈。除了调优数据库和调优SQL使之具有更佳的选择性(selectivity)之外,其他度量因素(比如连接池、运行时最小化元数据检索、移除长期运行的查询以分开线程、只有在必要时才提取blob)也可以优化数据访问性能,从而为任何数据密集型应用程序提供更好的响应能力。因此,根据应用程序的需要,选择合适的数据访问操作可以提高性能和可伸缩性。
下载
Name | Size | Downloadmethod | |||||||||
source.zip | 35.5KB | ||||||||||
关于作者 RameshTheivendran从1995年开始就是BorlandRADdatabaseconnectivityR&D小组成员。目前,他正在他们的Win32和.NET产品小组中致力于数据库连接性研究,并担任dbExpress和BorlandDataProvider(BDP)for.NET的架构师。 |
相关文章推荐
- ADO.NET 的数据存取性能(转转不要钱,呵呵…………)
- ADO.NET 的数据存取性能
- 使用ADO.NET2.0提升数据交互性能(1)
- 使用ADO.NET2.0提升数据交互性能(4)
- ASP.NET中数据存取性能优化
- 转载网上已篇关于linq to sq,entityframework,ado.net性能比较的文章
- ADO.net下几个提高数据访问性能的方法
- [转贴]使用ADO.NET2.0提升数据交互性能(http://dotnet.chinaitlab.com/ADONET/739268.html)
- 使用ADO.NET2.0提升数据交互性能(3)
- 使用ADO.NET2.0提升数据交互性能(5)
- 使用ADO.NET2.0提升数据交互性能
- 使用ADO.NET2.0提升数据交互性能(1)
- 使用ADO.NET2.0提升数据交互性能
- ADO.NET2.0提升数据交互性能
- 使用ADO.NET2.0提升数据交互性能(4)
- ado.net entity framework使用不同的方法查询数据的不同性能
- 使用ADO.NET2.0提升数据交互性能(1)
- 使用ADO.NET2.0提升数据交互性能 DataSet 数据表
- 利用ado.net存取BLOB数据
- 使用ADO.NET2.0提升数据交互性能(2)