面向程序員的數據庫訪問性能優化法則5

面向程序員的數據庫訪問性能優化法則5
2011年05月18日
  [b]3.3[/b][b]、設置[/b][b]Fetch Size[/b]
  當我們採用select從數據庫查詢數據時,數據默認並不是一條一條返回給客戶端的,也不是一次全部返回客戶端的,而是根據客戶端fetch_size參數處理,每次只返回fetch_size條記錄,當客戶端遊標遍歷到尾部時再從服務端取數據,直到最後全部傳送完成。所以如果我們要從服務端一次取大量數據時,可以加大fetch_size,這樣可以減少結果數據傳輸的交互次數及服務器數據準備時間,提高性能。
  以下是jdbc測試的代碼,採用本地數據庫,表緩存在數據庫CACHE中,因此沒有網絡連接及磁盤IO開銷,客戶端只遍歷遊標,不做任何處理,這樣更能體現fetch參數的影響:
  String vsql ="select * from t_employee";
  PreparedStatement pstmt = conn.prepareStatement(vsql,ResultSet.TYPE_FORWARD_ONLY,ResultSet.CONCUR_READ_ONLY);
  pstmt.setFetchSize(1000);
  ResultSet rs = pstmt.executeQuery(vsql);
  int cnt =rs.getMetaData().getColumnCount();
  Object o;
  while (rs.next()) {
  for (int i = 1; i中的employee表有100000條記錄,每條記錄平均長度135字節
  以下是測試結果,對每種fetchsize測試5次再取平均值:
  fetchsize
  elapse_time(s)
  1
  20.516
  2
  11.34
  4
  6.894
  8
  4.65
  16
  3.584
  32
  2.865
  64
  2.656
  [b]128[/b]
  [b]2.44[/b]
  256
  2.765
  512
  3.075
  1024
  2.862
  2048
  2.722
  4096
  2.681
  8192
  2.715
  
  
  
  Oracle jdbc fetchsize默認值爲10,由上測試可以看出fetchsize對性能影響還是比較大的,但是當fetchsize大於100時就基本上沒有影響了。fetchsize並不會存在一個最優的固定值,因爲整體性能與記錄集大小及硬件平臺有關。根據測試結果建議當一次性要取大量數據時這個值設置爲100左右,不要小於40。注意,fetchsize不能設置太大,如果一次取出的數據大於JVM的內存會導致內存溢出,所以建議不要超過1000,太大了也沒什麼性能提高,反而可能會增加內存溢出的危險。
  注:圖中fetchsize在128以後會有一些小的波動,這並不是測試誤差,而是由於resultset填充到具體對像時間不同的原因,由於resultset已經到本地內存裏了,所以估計是由於CPU的L1,L2 Cache命中率變化造成,由於變化不大,所以筆者也未深入分析原因。
  iBatis的SqlMapping配置文件可以對每個SQL語句指定fetchsize大小,如下所示:
  
  select * from employee
  [b]3.4[/b][b]、使用存儲過程[/b]
  大型數據庫一般都支持存儲過程,合理的利用存儲過程也可以提高系統性能。如你有一個業務需要將A表的數據做一些加工然後更新到B表中,但是又不可能一條SQL完成,這時你需要如下3步操作:
  a:將A表數據全部取出到客戶端;
  b:計算出要更新的數據;
  c:將計算結果更新到B表。
  如果採用存儲過程你可以將整個業務邏輯封裝在存儲過程裏,然後在客戶端直接調用存儲過程處理,這樣可以減少網絡交互的成本。
  當然,存儲過程也並不是十全十美,存儲過程有以下缺點:
  a、不可移植性,每種數據庫的內部編程語法都不太相同,當你的系統需要兼容多種數據庫時最好不要用存儲過程。
  b、學習成本高,DBA一般都擅長寫存儲過程,但並不是每個程序員都能寫好存儲過程,除非你的團隊有較多的開發人員熟悉寫存儲過程,否則後期系統維護會產生問題。
  c、業務邏輯多處存在,採用存儲過程後也就意味着你的系統有一些業務邏輯不是在應用程序裏處理,這種架構會增加一些系統維護和調試成本。
  d、存儲過程和常用應用程序語言不一樣,它支持的函數及語法有可能不能滿足需求,有些邏輯就只能通過應用程序處理。
  e、如果存儲過程中有複雜運算的話,會增加一些數據庫服務端的處理成本,對於集中式數據庫可能會導致系統可擴展性問題。
  f、爲了提高性能,數據庫會把存儲過程代碼編譯成中間運行代碼(類似於java的class文件),所以更像靜態語言。當存儲過程引用的對像(表、視圖等等)結構改變後,存儲過程需要重新編譯才能生效,在24*7高併發應用場景,一般都是在線變更結構的,所以在變更的瞬間要同時編譯存儲過程,這可能會導致數據庫瞬間壓力上升引起故障(Oracle數據庫就存在這樣的問題)。
  個人觀點:普通業務邏輯儘量不要使用存儲過程,定時性的ETL任務或報表統計函數可以根據團隊資源情況採用存儲過程處理。
  [b]3.5[/b][b]、優化業務邏輯[/b]
  要通過優化業務邏輯來提高性能是比較困難的,這需要程序員對所訪問的數據及業務流程非常清楚。
  舉一個案例:
  某移動公司推出優惠套參,活動對像爲VIP會員並且2010年1,2,3月平均話費20元以上的客戶。
  那我們的檢測邏輯爲:
  select avg(money) as avg_money from bill wherephone_no='13988888888' and date between '201001' and '201003';
  select vip_flag from member where phone_no='13988888888';
  if avg_money>20 and vip_flag=true then
  begin 執行套參();end; 如果我們修改業務邏輯爲:
  select avg(money) as avg_money from bill wherephone_no='13988888888' and date between '201001' and '201003';
  if avg_money>20 then
  begin select vip_flag from member wherephone_no='13988888888';
  if vip_flag=true then
  begin 執行套參(); end;end;
  通過這樣可以減少一些判斷vip_flag的開銷,平均話費20元以下的用戶就不需要再檢測是否VIP了。
  如果程序員分析業務,VIP會員比例爲1%,平均話費20元以上的用戶比例爲90%,那我們改成如下:
  select vip_flag from member where phone_no='13988888888';
  if vip_flag=true then
  begin
  select avg(money) as avg_money from billwhere phone_no='13988888888' and date between '201001' and '201003';
  if avg_money>20 then
  begin 執行套參(); end;end;這樣就只有1%的VIP會員纔會做檢測平均話費,最終大大減少了SQL的交互次數。
  以上只是一個簡單的示例,實際的業務總是比這複雜得多,所以一般只是高級程序員更容易做出優化的邏輯,但是我們需要有這樣一種成本優化的意識。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章