top
Loading...
提高DB2Web應用程序性能的五條規則

優秀的代碼行行相似,糟糕的代碼各個不同。

規則其實并不重要,重要的是養成良好習慣。

——譯者題記

簡介

速度和可升級性是網絡開發重要的性能要求,而且它們也不難達到。應用一些簡單的規則會提高網絡應用的性能。在本文中,您將了解到使用Borland® C#Builder和IBM® DB2®通用數據庫(UDB)8.1開發快速Microsoft® ASP.NET Web應用程序的5條規則。

分析Web應用程序的性能需要利用一些手段來檢測每個操作的性能。為此,我創建了一個TimeDiff類(見程序清單1),它可以計算數據庫操作的用時。你可以利用TimeDiff類的檢測結果作為衡量數據庫操作性能的基準,來觀察哪些操作最為有效。我還創建了一個配合TimeDiff 類使用的LOTSOFRECORDS表(見程序清單2),它包含10,000條記錄,你可以通過對它的操作來觀察不同技術之間的性能差異。DB2具有內部緩沖池,一旦運行一個查詢,內部緩沖池啟用,因此二次查詢的速度較快。在檢測查詢速度時,請忽略緩沖池啟用前的結果而采用啟用后的結果。

程序清單1. TimeDiff類

using System;namespace Effeciency{/// /// 這個類用于時間計算。在例子中,/// 我們將利用它檢測數據庫操作的速度,/// 以便作性能上的比較。 /// public class TimeDiff{DateTime StartTime;DateTime EndTime;public TimeDiff() {}public void Start(){StartTime = DateTime.Now;}public void Stop(){EndTime = DateTime.Now;}public string TimeDifferenceText{get{TimeSpan TimeDifference = EndTime - StartTime;return TimeDifference.ToString();}}}}

程序清單2. LOTSOFRECORDS的表定義

CREATE TABLE "GLENN "."LOTSOFRECORDS" ("KEYCOL" INTEGER NOT NULL , "COL1" CHAR(50) , "COL2" CHAR(50) , "COL3" CHAR(50) , "COL4" CHAR(50) , "COL5" CHAR(50) , "COL6" CHAR(50) , "COL7" CHAR(50) , "COL8" CHAR(50) , "COL9" CHAR(50) , "COL10" CHAR(50) ) IN "USERSPACE1" ; COMMENT ON TABLE "GLENN "."LOTSOFRECORDS" IS 'Table designed to Contain Lots of Records';-- DDL Statements for primary key on Table "GLENN "."LOTSOFRECORDS"ALTER TABLE "GLENN "."LOTSOFRECORDS" ADD CONSTRAINT "CC1058255334652" PRIMARY KEY("KEYCOL");

下面,我們就開始介紹這些提高DB2 UDB Web 應用程序性能的規則。我會先介紹提高數據庫性能的基本規則,然后列出一些適合以Borland Data Provider開發ASP.NET應用時的性能規則。

規則1:按需檢索

如果你只能記得住一條規則,那一定要記住這一條:按需檢索。如果你是“幸存者”電視節目的忠實觀眾,您會記得其中的參賽者通過限額分配來保證每個人都有充足的食物。這種做法對于數據庫開發同樣適用。如果你的應用程序能夠按需運行,那么就會合理地將數據庫和網絡資源留給其他應用。這聽起來很簡單,但我們還是來看一個例子

假設有一個包含10,000 行記錄和10 個字段的表,以及一個需要顯示所有記錄但只顯示3個字段的Web頁。很多開發者經常圖省事而使用"select *"語句選擇所有的字段:

select * from GLENN.LOTSOFRECORDS這種做法應當避免,而應力求只檢索需要的字段。可以在SQL 語句中定義要檢索的字段,例如:
 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, UNITPRICEFROM 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 datadataGrid1.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 Pagesstring 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 pagestring 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 contentsint 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.IMAGEURLFROM GLENN.PRODUCTLIST AS PRODUCTLISTWHERE 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 cursorDECLARE cursor1 CURSOR WITH RETURN FORSELECT PRODUCTLIST.PRODUCTCODE, PRODUCTLIST.PRODUCTNAME, PRODUCTLIST.DESCRIPTION, PRODUCTLIST.UNITPRICE, PRODUCTLIST.IMAGEURLFROM GLENN.PRODUCTLIST AS PRODUCTLISTWHERE PRODUCTLIST.CATEGORYCODE = CATCODE;-- Cursor left open for client applicationOPEN 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應用程序的性能。

作者:http://www.zhujiangroad.com
來源:http://www.zhujiangroad.com