網上看到的一篇文章,結合自己的經驗和大家交流一下
對大量數據的分頁處理
問題描述:
背景1:一客戶通過IE請求Web服務器查詢數據,而查詢結果是上千條甚至是上萬條記錄,要求查詢結果傳送到IE客戶端并分頁顯示。
背景2:一客戶通過IE或者其他方式請求Web服務器查詢數據,而查詢結果是上千條甚至是上萬條記錄,并要求查詢結果把包傳送到客戶的E-mail中。
問:對于這樣的有大量數據的結果集,在Web服務器端如何有效的處理?
可能涉及到的問題:
1. 內存占用
大量數據的結果集,可能要占用非常大的內存
2. 傳輸速度及策略
具體的分頁處理技術
處理方法
1 游標查詢 直接使用ResultSet來處理。
ResultSet是直接在數據庫上建立游標,然后通過ResultSet的行位置定位接口來獲得指定行位置的記錄。
當用戶第一請求數據查詢時,就執行SQL語句查詢,獲得的ResultSet對象及其要使用的連接對象都保存到其對應的會話對象中。
以后的分頁查詢都通過第一次執行SQL獲得的ResultSet對象定位取得指定行位置的記錄。
最后在用戶不再進行分頁查詢時或會話關閉時,釋放數據庫連接和ResultSet對象等數據庫訪問資源。
說明:
在用例分頁查詢的整個會話期間,一個用戶的分頁查詢就要占用一個數據庫連接對象和結果集的游標,這種方式對數據庫的訪問資源占用比較大,并且其利用率不是很高。 所有的數據庫產品。
優點:
減少了數據庫連接對象的多次分配獲取,減少了對數據庫的SQL查詢執行。
缺點:
占用數據庫訪問資源-數據庫連接對象,并占用了數據庫上的資源-游標。而這些資源都是十分寶貴的有限制的。
結論:
這種的數據庫查詢分頁處理方式不是最佳的。一般不適用這種方式。
2 定位行集SQL查詢 主要是直接使用數據庫產品的提供的對查詢的結果集可定位行范圍的SQL接口技術。
在用戶的分頁面查詢請求中,每次可取得查詢請求的行范圍的參數,然后使用這些參數生產取得指定行范圍的的SQL查詢語句,然后每次請求獲得一個數據庫連接對象并執行SQL查詢,把查詢的結果返回給用戶,最后釋放說有的數據庫訪問資源。
說明:
這種方式需要每次請求時都要執行數據庫的SQL查詢語句;對數據庫的訪問資源是使用完就立即釋放,不白白占用數據庫訪問資源。 對特定(提供了對查詢結果集可定位功能的)的數據庫產品。
如:Oracle,DB2, PostgreSQL,mySQL等。(MS SQL Server 沒有提供此技術。) 如:
1. Oracle數據庫使用關鍵字:rowid或rownum
2. DB2:
rowid或rownum ()
3. PostgreSQL 使用LIMIT 和 OFFSET
4. MySQL 使用Limit
優點:
這種技術是直接使用數據庫產品自己提供的可對查詢結果集定位行范圍過濾的功能,因此直接利用了數據庫的性能對此分頁查詢的優化功能。
對數據庫的訪問資源(數據庫連接對象,數據庫游標等)沒有浪費,這些資源的充分重復的利用。
對查詢的結果對Web容器沒有什么特別要求。
缺點:
要執行多次數據庫SQL查詢操作。對每次的分頁面操作請求都要指定相應范圍的結果集來執行SQL語句的數據庫查詢操作,這對數據庫有一定的影響。
對每次分頁面查詢請求要頻繁的從Web容器中獲得數據庫訪問資源(數據庫連接對象和數據庫游標)。
要依賴于具體的數據庫產品。因為對沒有實現沒有提供此技術的數據庫產品不能使用此方式。
結論:
由于每次對數據庫的SQL查詢操作相對而言耗用的數據資源比較少,并且在實際用戶的操作中,有可能用戶對查詢的所有結果集只是需要查看其中的部分頁面。因此這種方式是最佳的。
3 特別處理的定位行集SQL查詢 這種方式是在方式2的基礎上針對不提供對查詢結果集行范圍定位的數據庫產品。
其在Web容器端的操作邏輯大致和方式2相同。
只是先要對要查詢的數據庫表要有一字段的數據能區別每條不同的數據記錄。第一次查詢時,獲得用來可唯一標識不同記錄的字段的所有結果集,并緩存起來以備后面的分頁面查詢指定要查詢的結果集的行范圍。 主要是針對不同對查詢行集可定位范圍獲得的數據庫產品,如MS SQL Server等。 假設從A,B,C三個表中選取數據。且A有字段ID用來可唯一區別不同的記錄。
那么第一次查詢的時候,會查詢兩次
1. select A.id from A,B,C where condition.
2. 把A的ID緩存到SESSION中?3.從Session中?,F可按照次序來取得相應頁面范圍的ID來,并構造下一個查詢語句:select A.name, B.add from A,B,C where condition && ( A.ID in 本頁面范圍的 ID )
以后每次翻頁的時候,依次獲得對應頁的ID只要表中唯一的就可以了。無所謂大小,順序?這樣,SESSSION緩存的就只是一列而不是所有列了。當然,對于列數不多的,效果并不好。
也可使用存儲過程實現,可參照:http://expert.csdn.net/Expert/topic/2365/2365596.xml?temp=.7529261
優點:
同方式2
缺點:
同方式2;
還要在要查詢的數據庫表中建立一個相應的ID,用來唯一區別每條記錄。
結論:
同方式2。
4 緩存一次SQL查詢的結果集
優點:
缺點:
既然我們要緩存結果,那么用戶就可能會看到過期的數據
說明:
對于實際情況的應用來說,一般結合實際情況,結合使用方式2(或方式3)和方式4。如:一個應用場景:對公司的產品的查詢是經常的,但是產品的種類不是很多,這時可使用緩存方式;但是對有些查詢結果集較大,數據庫和Web容器之間的網絡訪問由可能是遠程的,這時候可考慮使方式2(或者方式3)。
//-----------------------------------------------------------
以前做的幾個項目中,大多數都是采用類似于方式1的分頁。如果查詢頻繁的話,系統訪問速度就是問題。
方法2確實是個好方法,但是過于依賴數據庫,并非文章中所提及的“最佳方法”。
個人認為方法1和方法2相結合,才能達到較好的效果。即先查詢出較大的結果集(可能是最大集合的1/100),然后對該結果集使用方法1的分頁。這樣既不用頻繁的訪問數據庫,也不會創造出過多的對象。當然正如文章中提到的:MS SQL Server 不支持定位技術 >_< ,所以游標的定位需要靠程序解決。
方法3好象是方法4的補充了。。。。研究ing
下面是方法4的實現
//關于分頁顯示中,因對象過多而造成的系統速度過慢問題的修改,適合數據庫更新不頻繁的情況
//得到一個idList
List idList = new ArrayList();
rs = select id from db where condition
while (rs.next){
idList.add(rs.getString("id"));
}
//根據idList得到formList
List formList = TEST.getInstance().getInfo(idList);
//類TEST中具體實現
//創建唯一的instance,只有當系統第一次調用此方法,則創建實例
Map m = new Treemap();
rs = select * from db
while (rs.next){
TestForm testForm = new TestForm();
testForm.setvalue(...);
m.put(TestForm.getF_id(),testForm);
}
//根據idList從map中取得相應數據
public List getInfo(List idList){
List infoList = new ArrayList();
for (int i = 0;i
infoList.add(m.get(id));
}
return infoList;
}
//顯示formList中的內容
//顯示ListForm中的具體內容
不足之處,多多指教。-_-!