select KEYCOL, COL1, COL2, COL7 from GLENN.LOTSOFRECORDS 在本文附带的源程序中,有两个ASP.NET页面:一个是RetrievingAllFields.aspx,它执行第一个查询;另一个是RetrievingLimitedFields.aspx ,它执行第二个查询,即只检索需要的字段。 用TimeDiff类进行检测,执行第一个查询用了1.622 秒,执行第二个查询用了1.311秒。后者用时只是前者的80%,不仅用时少,而且还减少了Web应用程序和数据库服务器之间的网络数据堵塞。 这个例子只限制了检索的字段,你还可以使用WHERE语句限制检索的记录数。WHERE 语句可以限制服务器返回的记录数(见程序清单3)。要记住,通过网络发送的记录数据越少,对应用程序、数据库、用户和网络越有好处。 规则2:优化数据库 有时候你的Web应用程序可能运行得不错,但你想让它更好。一个简单的减少搜索时间的方法是为特定字段创建索引。如果有一个查询是要搜索某个价格范围内的产品(见程序清单3),但你没有为价格字段定义索引,那么返回数据就会多花一些时间。而一旦建立了索引,DB2会很快返回你想要的结果。 程序清单3. 利用索引进行数据库搜索 SELECT PRODUCTCODE, PRODUCTNAME, DESCRIPTION, UNITPRICE FROM GLENN.PRODUCTLIST WHERE UNITPRICE > 20.00 优化数据库不只是为搜索字段创建索引这么一条,你应当尽可能多地搜集相关的DB2信息以使应用程序运行得更好。经常访问IBM发者园地(IBM DB2 Developer Domain)和comp.databases.ibm-db2(comp.databases.ibm-db2)等一些相关的Web站点或新闻组,对于保持DB2开发技巧不断更新是一个很好的办法。 你还应当努力熟悉DB2附带的工具,例如DB2索引建议器(Index Advisor)。DB2索引建议器可以根据你递交的查询和所连接的数据库返回最佳索引列表。 规则3:使用DB2 UDB的OLAP功能改善分页 在ASP.NET中,一个最常见的操作是表格分页显示。ASP.NET中DataGrid组件的默认设置是将表格需要的所有记录都返回客户端,然后再根据选择的页显示相应记录. 程序清单4 使用DataGrid 内建分页机制 TimeDiff diff = new TimeDiff(); private DataSet GetProductsDataSet() { diff.Start(); string connString = ConfigurationSettings.AppSettings["database"]; BdpConnection conn = new BdpConnection(connString); BdpDataAdapter da = new BdpDataAdapter("select KEYCOL, " + "COL1, COL2, COL7 FROM GLENN.LOTSOFRECORDS "+ "ORDER BY KEYCOL ASC", conn); DataSet ds = new DataSet(); da.Fill(ds, "Table1"); diff.Stop(); return ds; } private void BindToTheData() { dataGrid1.DataSource = GetProductsDataSet(); dataGrid1.DataMember = "Table1"; dataGrid1.DataBind(); label1.Text = diff.TimeDifferenceText; } private void Page_Load(object sender, System.EventArgs e) { if (!IsPostBack) { BindToTheData(); } } private void dataGrid1_PageIndexChanged(object source, System.Web.UI.WebControls.DataGridPageChangedEventArgs e) { //Change the active page of the data dataGrid1.CurrentPageIndex = e.NewPageIndex; BindToTheData(); } 如果以LOTSOFRECORDS表为例,从数据库中检索1万条记录很快就会占尽网络带宽,尤其是一次只需要查看10条或20条记录时。用户会发现加载ASP.NET页面的时间太长,而且很可能会超时。试想,如果数据库有数百万条记录,那你的应用程序就会运行缓慢只至停止执行。因此,需要采取更好的方法来检索数据。 还好,DataGrid组件允许定制分页。你可以计算哪些记录需要显示,然后只从服务器检索出相应的记录。 后来版本的DB2 UDB都具有一个很大的特点,那就是OLAP函数,它允许你进行各种记录检索。例如,只检索某些记录可以执行如下的查询: SELECT * FROM (SELECT KEYCOL, COL1, COL2, COL7, rownumber() over(ORDER BY KEYCOL ASC) AS rn FROM GLENN.LOTSOFRECORDS ORDER BY KEYCOL ASC) AS a1 WHERE a1.rn BETWEEN 100 AND 120 使用DataGrid定制分页时,需要获得特定页的DataSet。GetDataByPage方法可以检索结果集而不管分页数和分页大小: private DataSet GetDataByPage(int PageNo, int PageSize, out int NumberOfPages) { int startRecord = (PageNo - 1) * PageSize + 1; int endRecord = startRecord + PageSize - 1; string connString = ConfigurationSettings.AppSettings["database"]; BdpConnection conn = new BdpConnection(connString); conn.Open(); //Get the number of Pages string sRecordCount = "select count(*) from GLENN.LOTSOFRECORDS"; BdpCommand cmdGetRecordCount = new BdpCommand(sRecordCount, conn); int intRecordCount = (int)cmdGetRecordCount.ExecuteScalar(); NumberOfPages = intRecordCount / PageSize; if (intRecordCount % PageSize > 0) NumberOfPages++; //Get the data specifically for the page string sSQL = "SELECT * FROM " + " (SELECT KEYCOL, COL1, COL2, COL7, rownumber() " + " over(ORDER BY KEYCOL ASC) AS rn " + " FROM GLENN.LOTSOFRECORDS " + " ORDER BY KEYCOL ASC) AS a1 " + " WHERE a1.rn BETWEEN ? AND ?"; BdpCommand cmdSel = new BdpCommand(sSQL, conn); BdpParameter prmStart = cmdSel.Parameters.Add("StartRecord", BdpType.Int32); prmStart.Value = startRecord; BdpParameter prmEnd = cmdSel.Parameters.Add("EndRecord", BdpType.Int32); prmEnd.Value = endRecord; BdpDataAdapter da = new BdpDataAdapter(cmdSel, conn); DataSet ds = newDataSet(); da.Fill(ds, "Table1"); diff.Stop(); return ds; } private void LoadSingleDataPage(int pageNo) { //Display the Page contents int PageCount; DataSet dsData = GetDataByPage(pageNo+1, dataGrid1.PageSize, out PageCount); dataGrid1.VirtualItemCount = PageCount * dataGrid1.PageSize; dataGrid1.CurrentPageIndex = pageNo; dataGrid1.DataSource = dsData; dataGrid1.DataBind(); } private void Page_Load(object sender, System.EventArgs e) { if (!IsPostBack) { LoadSingleDataPage(0); } } private void dataGrid1_PageIndexChanged(object source, System.Web.UI.WebControls.DataGridPageChangedEventArgs e) { LoadSingleDataPage(e.NewPageIndex); } 通过定制分页,将检索记录数从1万条限制到20条,占用的网络资源约为最初的0.2%。在我的本机用TimeDiff 类检测,使用定制分页时用时0.5~0.7秒,而使用默认分页机制时用时为0.9~1.5秒。 规则4:使用存储过程 如果发送一条SQL语句到DB2服务器,其执行过程如下: (1)DB2 UDB Server对SQL语句进行语法检查; (2)生成存储过程执行计划; (3)数据返回应用程序。 当使用存储过程时,前两步过程已经完成。存储过程经编译后,调用时只将存储过程名和参数传递给数据库服务器。因此,执行时间的减少赢得了性能上的优势。但这种优势只有当返回的结果集非常大时才有所体现。 下面举一个例子。先建立一个PRODUCTLIST表: CREATE TABLE "GLENN "."PRODUCTLIST" ( "PRODUCTCODE" VARCHAR(20) NOT NULL , "PRODUCTNAME" VARCHAR(50) NOT NULL , "DESCRIPTION" VARCHAR(255) , "UNITPRICE" DOUBLE NOT NULL , "CATEGORYCODE" INTEGER , "IMAGEURL" CHAR(150) ) IN "USERSPACE1" ; COMMENT ON TABLE "GLENN "."PRODUCTLIST" IS 'A list of Products in the Shopping Cart'; -- DDL Statements for primary key on Table "GLENN "."PRODUCTLIST" ALTER TABLE "GLENN "."PRODUCTLIST" ADD CONSTRAINT "CC1053568050795" PRIMARY KEY ("PRODUCTCODE"); 然后进行如下查询: SELECT PRODUCTLIST.PRODUCTCODE, PRODUCTLIST.PRODUCTNAME, PRODUCTLIST.DESCRIPTION, PRODUCTLIST.UNITPRICE, PRODUCTLIST.IMAGEURL FROM GLENN.PRODUCTLIST AS PRODUCTLIST WHERE PRODUCTLIST.CATEGORYCODE = 2; 以上查询很容易转为存储过程。DB2开发中心(DB2 Development Center)包含一个优秀的存储过程向导,它可以让你轻松生成存储过程。 存储过程向导启动后,你只需要选择相应的表、字段和规则,存储过程的创建过程由向导来完成。 存储过程向导的一个极为有用之处是可以利用它轻松创建存储过程的输入参数。创建一个SQL 存储过程(DB2 还能创建Java存储过程),可以选择“Category Code”作为存储过程的输入参数。 完成后,所创建的存储过程如下: CREATE PROCEDURE GLENN.GETPRODUCTSINCATEGORY ( IN CATCODE INTEGER ) DYNAMIC RESULT SETS 1 ------------------------------------------------------------------------ -- SQL Stored Procedure ------------------------------------------------------------------------ P1: BEGIN -- Declare cursor DECLARE cursor1 CURSOR WITH RETURN FOR SELECT PRODUCTLIST.PRODUCTCODE, PRODUCTLIST.PRODUCTNAME, PRODUCTLIST.DESCRIPTION, PRODUCTLIST.UNITPRICE, PRODUCTLIST.IMAGEURL FROM GLENN.PRODUCTLIST AS PRODUCTLIST WHERE PRODUCTLIST.CATEGORYCODE = CATCODE; -- Cursor left open for client application OPEN cursor1; END P1 使用存储过程向导时,可以让存储过程返回不止一条查询的结果。通过这种方法,可以发挥存储过程的最大优势。如果结果集很小,使用存储过程可能要慢于使用查询。你应当不断对数据访问作性能测试。 使用Borland Data Provider调用存储过程与调用查询有所不同,主要是BdpDataAdapter的BdpCommand对象必须要将CommandType设为CommandType.StoredProcedure 并将CommandText设为存储过程名。此外,还需要在BdpCommand对象的参数集合中定义存储过程参数。 一旦定义好参数,只需使用BdpDataAdapter的Fill 方法填充DataSet: private void GetProductsViaStoredProcedure(int CategoryCode) { cmdGetDataViaStoredProc.Parameters[0].Value = CategoryCode; BdpDataAdapter da = new BdpDataAdapter(cmdGetDataViaStoredProc); da.Fill(dsProducts, "Products"); dataGrid1.DataBind(); } 规则5:尽可能使用缓存 ASP.NET最大的特点之一是缓存。缓存的原理很简单,将经常访问的内容储存在内存中,访问时不需要再到数据库或通过网络去检索数据。访问内存中的信息总是要比通过其它过程或网络访问资源要快。 那么如何使用缓存呢?在ASP.NET中有几种方法。一种方法是在ASP.NET页面头部定义一个页面指示标识,让其自动管理页面缓存。如果你读过我的上一篇文章《利用IBM DB2 UDB建立ASP.NET站点》(Build ASP.NET Web Sites with IBM DB2 Universal Database),应当很熟悉这种技术。通过包含OutputCache指令,可以缓存整页: 另一种方法是使用Pages对象内建的Cache 对象。将内容放入缓存时,使用Cache 对象的Insert方法;从缓存取出内容时,使用Cache对象的默认集合。 private void Page_Load(object sender, System.EventArgs e) { TimeDiff diff = new TimeDiff(); diff.Start(); string retrievalMethod; DataSet CategoriesDataSet; if (Cache["Categories"] == null) { retrievalMethod = "Database"; connShopping.Open(); CategoriesDataSet = new DataSet(); daCategories.Fill(CategoriesDataSet, "Categories"); Cache.Insert("Categories", CategoriesDataSet); connShopping.Close(); } else { retrievalMethod = "Cache"; CategoriesDataSet = (DataSet)Cache["Categories"]; } diff.Stop(); lblDetails.Text = "Retrieval from the " + retrievalMethod + " in " + diff.TimeDifferenceText + " seconds"; dataGrid1.DataSource = CategoriesDataSet; dataGrid1.DataMember = "Categories"; dataGrid1.DataBind(); } 当页面第一次被请求时,我在本机测试的数据检索用时为0.9秒,但从缓存中检索数据几乎没用什么时间,因为TimeDiff类的检测结果为00:00:00。可见,使用缓存是加速Web应用简单而有效的方法。 如果需要定时更新数据,还以使用缓存对象的过期策略。只需要使用Insert 方法的重载版本,指定过期时间即可。下面是每隔6小时自动刷新“Categories”缓存的一种方法: Cache.Insert("Categories", CategoriesDataSet, null, System.Web.Caching.Cache.NoAbsoluteExpiration, TimeSpan.FromHours(6)); 总结 在完美的世界里,应用程序总是执行快速,支持无限用户,而且不占任何网络资源。但我们并非置身其中,因此你应当很好地利用本文所概括的有效规则,努力提高Web应用程序的性能。(原作者:Glenn Stephens)(中国程序员)
|