程序優化小結

目錄
文檔控制 2
1. 程序優化小結 4
1.1. 查找效率低的主要瓶頸代碼 4
1.2. 分析及優化瓶頸代碼 7
1.3. sql優化相關 12

  1. 程序優化小結
    客戶化的程序包很可能隨着數據量的增大會變得越來越慢,除此之外,在使用的過程
    中需要添加新的邏輯,也可能導致程序運行效率降低。因此,在項目上對一些老從客
    戶化程序包進行優化應該還是很常見的。
    這兩週項目上優化了兩個老的計算程序,發現程序程序優化是一個很大的課題,還有
    很多不懂和需要學習的地方。暫且把這段時間學到的東西稍微作一個小結,希望對大
    家有所幫助;本次文檔中部分內容參考網上資料以及其他程序優化文檔;

1.1. 查找導致效率低的瓶頸代碼
俗話說,磨刀不誤砍柴工,優化程序前我們先得花一些時間搞懂程序效率低的原因。這一點很重要,因爲如果沒弄清楚程序爲什麼運行慢,就興沖沖的想去優化,很可能會走很多彎路,時間花費了很多不說,還沒有很好的效果;
如何查找導致效率低的瓶頸代碼呢,我覺得很方便的是創建自治事務的存儲過程,在該過程中給日誌表寫入消息,在程序的重要的節點上寫上相應的日誌和時間,運行一次程序,再對日誌表進行輸出分析,可以很方便的看出來哪一段代碼導致程序效率低;如果覺得創建日記表的過程很麻煩,直接使用dbms_output.put_line輸出日誌和當前系統時間也是可以的。
1.1.1. 創建日誌表

– Create table
create table AGM_BG_RUN_LOG
(
id NUMBER,
request_id NUMBER,
log_content VARCHAR2(2000),
creation_date DATE default sysdate not null,
created_by NUMBER default -1 not null,
last_updated_by NUMBER default -1 not null,
last_update_date DATE default sysdate not null,
attribute1 VARCHAR2(500),
attribute2 VARCHAR2(500),
attribute3 VARCHAR2(500),
attribute4 VARCHAR2(500),
attribute5 VARCHAR2(500),
attribute6 VARCHAR2(500),
attribute7 VARCHAR2(500),
attribute8 VARCHAR2(500),
attribute9 VARCHAR2(500),
attribute10 VARCHAR2(500),
attribute11 VARCHAR2(500),
attribute12 VARCHAR2(500),
attribute13 VARCHAR2(500),
attribute14 VARCHAR2(500),
attribute15 VARCHAR2(500),
attribute16 VARCHAR2(500),
attribute17 VARCHAR2(500),
attribute18 VARCHAR2(500),
attribute19 VARCHAR2(500),
attribute20 VARCHAR2(500),
attribute21 VARCHAR2(500),
attribute22 VARCHAR2(500),
attribute23 VARCHAR2(500),
attribute24 VARCHAR2(500),
attribute25 VARCHAR2(500),
attribute26 VARCHAR2(500),
attribute27 VARCHAR2(500),
attribute28 VARCHAR2(500),
attribute29 VARCHAR2(500),
attribute30 VARCHAR2(500),
version NUMBER default 0 not null
)
tablespace JEP_TS_DATA
pctfree 10
initrans 1
maxtrans 255
storage
(
initial 64K
next 1M
minextents 1
maxextents unlimited
);
– Add comments to the table
comment on table AGM_BG_RUN_LOG
is ‘請求運行日誌’;
– Add comments to the columns
comment on column AGM_BG_RUN_LOG.id
is ‘主鍵’;
comment on column AGM_BG_RUN_LOG.request_id
is ‘執行請求’;
comment on column AGM_BG_RUN_LOG.log_content
is ‘日誌內容’;
– Create/Recreate indexes
create unique index AGM_BG_RUN_LOG_U1 on AGM_BG_RUN_LOG (ID)
tablespace JEP_TS_INDEX
pctfree 10
initrans 2
maxtrans 255
storage
(
initial 64K
next 1M
minextents 1
maxextents unlimited
);

1.1.2. 創建自治事務的存儲過程

–程序運行日誌
procedure log(p_job_id in number, p_message in varchar2) is
pragma autonomous_transaction;
begin
DBMS_OUTPUT.PUT_LINE(P_MESSAGE);
insert into agm_bg_run_log
(id, request_id, log_content)
values
(agm_bg_run_log_s.nextval, p_job_id, p_message);
commit;
end log;

1.1.3. 在關鍵節點上寫上日誌
寫日誌的地方除了程序開始和結束的位置,還有程序裏面你覺得可能導致程序效率低的節點。
1.1.4. 分析日誌
運行完程序後,查詢出日誌表進行分析;
SELECT *
FROM agm_bg_run_log t
WHERE t.request_id = 2048658
ORDER BY t.creation_date, t.id;
一般情況下查找跨度最大的時間,定位兩條日誌之間的代碼,就能找到導致效率低下的代碼;

但是有時候則不一定時間跨度最大的就是效率瓶頸段代碼,比如一個程序運行了一個小時,程序主要結構是一個cursor循環裏面有很多邏輯的驗證和處理,結構如下:

tnnt_common_utl.log(p_job_id, ‘日誌1’);
for rec in (..) loop
tnnt_common_utl.log(p_job_id, ‘日誌2 ‘);
…;–各種處理邏輯
end loop;
tnnt_common_utl.log(p_job_id, ‘日誌3’);

日誌1輸出到日誌到第一次日誌2輸出的時間爲五六分鐘,日誌2之間的時間大概幾秒鐘甚至更短,最後日誌3輸出時程序的總共時間就達到一個小時了;這時候我們可能會把主要精力放到cursor優化上面,實際這樣的話方向就有點搞錯了,因爲本次運行雖然日誌1輸出到日誌到第一次日誌2輸出的時間最長,日誌2之間的輸出時間跨度並不大,但是因爲循環的數據量很大,很小的時間累積之後就變得很可怕了,我們會發現日誌2第一次輸出到日誌3的輸出時間佔了五十幾分鍾,因此優化cursor循環裏面的代碼才應該是我們最先關注的。
值得注意的是,cursor裏面的sql應該是一次性查詢出來的,而不是查詢一條循環一次;舉個例子我們常會用一下的代碼來鎖表記錄
FOR rec IN (SELECT *
FROM table_name
where……
FOR UPDATE NOWAIT
)
LOOP
EXIT;
END LOOP;
第一次進入循環我們就exit了,但是可以鎖到所有符合where條件的記錄,說明cursor一開始就已查詢出所有的記錄;
以上,我們就可以確定日誌1到日誌2的時間就是cursor裏面sql運行的時間,後面處理的時間與cursor裏的查詢sql就沒多大關係了。

1.2. 分析及優化瓶頸代碼
1.2.1. 將瓶頸代碼段單獨取出來分析
1、如果瓶頸代碼不僅僅是個查詢sql,我一般會編寫一個匿名塊,將瓶頸代碼放入匿名塊中進行分析;
2、測試環境中在分析測試代碼時需要注意,每次運行過後應該清除一次本次session產生的緩存,否則會影響測試的時間,清除緩存的代碼如下:
ALTER SESSION SET EVENTS ‘immediate trace name flush_cache’;
如果在正式環境測試查詢語句,則需要注意不可隨意清除緩存。
3、如果查詢sql涉及的表很大,且表數量較多,很可能測試環境的執行計劃和正式環境的執行計劃是不同的;除了可能是兩個環境表的數據量不同這個原因,還可能與兩個環境的數據庫服務器性能不同有關係;執行計劃不同,就會導致同一個sql在兩個環境的運行時間相差很大,使得測試環境的測試結果沒有很大的參考意義,就不得不到正式環境去測試sql的性能了。
4、分析代碼時,如果覺得執行計劃不是想要的結果也可以嘗試對sql裏面的表進行數據收集,還可以使用hint強制oracle執行某個索引,指定某些表爲驅動表等;數據收集和hint的使用將在1.3節中進行介紹。
1.2.2. 優化瓶頸代碼

1、情景一:對於以下這種結構代碼

tnnt_common_utl.log(p_job_id, ‘日誌1’);
for rec in (..) loop
tnnt_common_utl.log(p_job_id, ‘日誌2 ‘);
1.驗證1
2.驗證2
3.處理1
4.處理2
…;–各種處理邏輯
end loop;
tnnt_common_utl.log(p_job_id, ‘日誌3’);

如果是因爲循環裏面的處理邏輯導致效率的降低,可以嘗試批量處理方式,同時可以嘗試先將cursor裏面的數據插入到臨時表中,之後的操作都基於臨時表,插入臨時表的操作可以使用FETCH … BULK COLLECT INTO處理結構,可以提高插入的效率;插入事例如下:
PROCEDURE populate_sales IS

CURSOR cur_line IS
  SELECT ... FROM ...;

TYPE l_line_tbl_type IS TABLE OF cur_line%ROWTYPE INDEX BY BINARY_INTEGER;
l_line_tbl l_line_tbl_type;

l_loop_bool BOOLEAN := FALSE;

BEGIN

-- start
DELETE FROM jep_tnnt_sales_temp t WHERE t.batch_id = p_batch_id;
l_line_tbl.delete;

OPEN cur_line;
LOOP
  FETCH cur_line BULK COLLECT
    INTO l_line_tbl LIMIT 1000;
  l_loop_bool := cur_line%NOTFOUND;

  IF l_line_tbl.count > 0 THEN
    FORALL i IN l_line_tbl.first .. l_line_tbl.last
      INSERT INTO jep_tnnt_sales_temp
        (batch_id,
         project_num,
         sales_header_id,
         payment_method,
         deal_amount,
         tenant_num,
         premise_num,
         deal_date,
         key_cooperation_mode,
         attribute1, --刷卡店鋪
         attribute2, --sales_payment_line_id
         attribute3, --source_header_id
         attribute4 --card_type
         )
      VALUES
        (p_batch_id,
         l_line_tbl(i).project_num,
         l_line_tbl(i).sales_header_id,
         l_line_tbl(i).payment_method,
         l_line_tbl(i).deal_amt,
         l_line_tbl(i).tenant_num,
         l_line_tbl(i).premise_num,
         l_line_tbl(i).deal_date,
         l_line_tbl(i).key_cooperation_mode,
         l_line_tbl(i).deal_premise_num,
         l_line_tbl(i).sales_payment_line_id,
         l_line_tbl(i).source_header_id,
         l_line_tbl(i).card_type);

    l_line_tbl.delete;
  END IF;
  EXIT WHEN l_loop_bool;
END LOOP;
CLOSE cur_line;
l_line_tbl.delete;

END populate_sales;
優化的處理結構:

–1.將cursor的數據fetch到臨時表
–2.基於臨時表批量驗證1
–3.基於臨時表批量驗證3
–4.基於臨時表批量處理1
–5.基於臨時表批量處理2

2、情景二:如果大數據表A和大數據表B關聯,且SQL的where過濾條件主要和A表有關、需要篩選出的數據量相對較小,那麼有兩種辦法可提高效率:
1) 可以考慮建立一個臨時表,先根據where條件篩選A表的數據後,再用B表和A的臨時表做關聯取數。對於大數據量建議採用臨時表,遊標是要佔用系統資源的,而且記錄數目越多佔用的資源越多,臨時表不全都是存放在硬盤的。當你讀取的時候,它是一塊(頁)的讀出來的。這些都是佔留內存的。
2) 可以考慮建立一個遊標,先根據where條件篩選A表的數據到一個遊標,在循環遊標裏和B表作關聯。遊標查詢出的數據少於3000條時建議用遊標。
3、情景三:如果sql裏面表數量很多也可以借鑑2中的方法進行拆分,將小數據量的幾張表和大數據量的幾張表分開嵌套循環;外面是小表的關聯cursor,裏面是大表的關聯sql;同樣也可以嘗試將小表關聯的數據插入臨時表,在和幾張大表進行關聯;
4、 情景四:查詢視圖異常緩慢(視圖內包含大數據表)
情景:現有的以下查詢SQL速度非常慢,例:
select *
from my_view tb
where tb.tb_big_id = 123;
視圖my_view的創建語句爲:
select *
from tb_big_table big,
tb_small_table1 small1,
tb_small_table2 small2
where big.tb_big_id = small1.tb_big_id
and small1.tb_small_id1 = small2.tb_small_id1;
思路:將查詢視圖的SQL的where子句中和大數據表相關的條件放到視圖自身的SQL裏,如此一來可以縮小大數據表的數據量再和其他表關聯。
策略:(1)利用面向對象的方法,且同一個會話Session中可以對同一個變量進行存取值,首先定義一個程序包,包含大數據表的條件的局部變量(需初始化值爲不存在的一個值)以及對該變量進行賦值的過程、取值的函數。例:
create or replace package my_view_pkg is
– 賦值過程
procedure tb_big_id(pi_id number);
– 取值函數
function tb_big_id return number;
end my_view_pkg;
/
create or replace package body my_view_pkg is
– 定義局部變量 並設置初始值
ln_tb_big_id number := -1;
– 賦值過程
procedure tb_big_id(pi_id number) is
begin
ln_tb_big_id := tb_big_id;
end tb_big_id;
– 取值函數
function tb_big_id return number is
begin
return tb_big_id;
end tb_big_id;
end my_view_pkg;
/
(2)修改原有的視圖的SQL,用剛建立的包裏的取值函數作爲條件的一端。列:
create or replace view my_view as
select *
from tb_big_table big,
tb_small_table1 small1,
tb_small_table2 small2
where big.tb_big_id = my_view_pkg.tb_big_id
and big.tb_big_id = small1.tb_big_id
and small1.tb_small_id1 = small2.tb_small_id1;
(3)在查詢該視圖前,爲該視圖內引用的變量進行事先賦值。例:
begin
– 先賦值
my_view_pkg.tb_big_id = 123;
– 查詢結果和原視圖效果一致,效果卻大大提升
select * from my_view;
end;
1.2.3. 將優化後的代碼替換源程序的瓶頸代碼

優化達到代碼結束後,將優化後的代碼替換原有代碼進行測試;

1.3. sql優化相關知識
1.3.1. 統計信息收集(數據收集)
1.在給sql做執行計劃前,如果擔心當前表的統計信息不是最新的,可以對這些表進行數據收集, 就是收集表和索引的信息,CBO根據這些信息決定SQL最佳的執行路徑。通過對錶的分析,可以產生一些統計信息,通過這些信息oracle的優化程序可以優化。
表級別的數據收集的函數如下:
DBMS_STATS.GATHER_TABLE_STATS ( ownname VARCHAR2, tabname VARCHAR2, partname VARCHAR2, estimate_percent NUMBER, block_sample BOOLEAN, method_opt VARCHAR2, degree NUMBER, granularity VARCHAR2, cascade BOOLEAN, stattab VARCHAR2, statid VARCHAR2, statown VARCHAR2, no_invalidate BOOLEAN, force BOOLEAN);
參數說明:
ownname:要分析表的擁有者
tabname:要分析的表名.
partname:分區的名字,只對分區表或分區索引有用.
estimate_percent:採樣行的百分比,取值範圍[0.000001,100],null爲全部分析,不採樣. 常量:DBMS_STATS.AUTO_SAMPLE_SIZE是默認值,由oracle絕定最佳取採樣值.
block_sapmple:是否用塊採樣代替行採樣.
method_opt:決定histograms信息是怎樣被統計的.method_opt的取值如下:
for all columns:統計所有列的histograms.
for all indexed columns:統計所有indexed列的histograms.
for all hidden columns:統計你看不到列的histograms
for columns SIZE | REPEAT | AUTO | SKEWONLY:統計指定列的histograms.N的取值範圍[1,254]; REPEAT上次統計過的histograms;AUTO由oracle決定N的大小;SKEWONLY multiple end-points with the same value which is what we define by “there is skew in the data
degree:決定並行度.默認值爲null.
granularity:Granularity of statistics to collect ,only pertinent if the table is partitioned.
cascace:是收集索引的信息.默認爲falase.
stattab指定要存儲統計信息的表,statid如果多個表的統計信息存儲在同一個stattab中用於進行區分.statown存儲統計信息 表的擁有者.以上三個參數若不指定,統計信息會直接更新到數據字典.
no_invalidate: Does not invalidate the dependent cursors if set to TRUE. The procedure invalidates the dependent cursors immediately if set to FALSE.
force:即使表鎖住了也收集統計信息.
例子:

execute dbms_stats.gather_table_stats(ownname => ‘owner’,tabname => ‘table_name’ ,estimate_percent => null ,method_opt => ‘for all indexed columns’ ,cascade => true);

execute dbms_stats.gather_table_stats(ownname => ‘boss0817’,tabname => ‘t_subscriberelations’,estimate_percent => null ,method_opt => ‘for all indexed columns’ ,cascade => true);
2、EBS中也可以提交併發請求進行統計信息的收集
Oracle ERP中有幾個與Gather有關的標準Request:
所有列信息數據統計:
Gather All Column Statistics –FND_STATS.GATHER_ALL_COLUMN_STATS()
某張表的列信息數據統計:
Gather Column Statistics –FND_STATS.GATHER_COLUMN_STATS()
schema級別的信息數據統計:
Gather Schema Statistics –FND_STATS.GATHER_SCHEMA_STATS()
表級別信息數據統計:
Gather Table Statistics –FND_STATS.GATHER_TABLE_STATS()
查看FND_STATS 這個Package的寫法,其實它就是在調用Oracle DB中Standard的Package dbms_stats 中的某些Function。
參考網上資料:1.http://blog.csdn.net/cnham/article/details/5724934
2. http://blog.itpub.net/26892340/viewspace-721935/
3. http://www.cnblogs.com/yangzw478/archive/2012/12/11/2812508.html
1.3.2. hint的使用
hints是oracle提供的一種機制,用來告訴優化器按照我們的告訴它的方式生成執行計劃。
1.索引控制
/+ INDEX(TABLE INDEX1) /
/+ NO_INDEX(TABLE INDEX1, index2) /
/+ INDEX(mmt,index1) INDEX(mtln,index2) /
表明對錶選擇索引的掃描方法。
第1種是指定SQL要走的索引,不過注意使用別名,且條件中應該存在索引字段的條件,事例如下:

第2種表示禁用某個索引,特別適合於準備刪除某個索引前的評估操作。

第3種表示指定多個表的多個索引。

2.驅動表控制:/+ ORDERED /
FROM子句中默認最後一個表是驅動表,ORDERED將from子句中第一個表作爲驅動表. 特別適合於多表連接非常慢時嘗試。例如:
SELECT /+ ORDERED / A.COL1,B.COL2,C.COL3 FROM TABLE1 A,TABLE2 B,TABLE3 C WHERE A.COL1=B.COL1 AND B.COL1=C.COL1;
1.3.3. 連接方式
使用CBO 時,要注意看採用了哪種類型的表連接方式。ORACLE的共有Sort Merge Join(SMJ)、Hash Join(HJ)和Nested Loop Join(NL);
1、Nested Loop Join(NL)
對於被連接的數據子集較小的情況,nested loop連接是個較好的選擇。nested loop就是掃描一個表,每讀到一條記錄,就根據索引去另一個表裏面查找,沒有索引一般就不會是 nested loops。一般在nested loop中, 驅動表滿足條件結果集不大,被驅動表的連接字段要有索引,這樣就走nstedloop。如果驅動表返回記錄太多,就不適合nested loops了。如果連接字段沒有索引,則適合走hash join,因爲不需要索引。
可用ordered提示來改變CBO默認的驅動表,可用USE_NL(table_name1 table_name2)提示來強制使用nested loop。

2、HASH JOIN
hash join是CBO 做大數據集連接時的常用方式。優化器掃描小表(或數據源),利用連接鍵(也就是根據連接字段計算hash 值)在內存中建立hash表,然後掃描大表,每讀到一條記錄就來探測hash表一次,找出與hash表匹配的行。
當小表可以全部放入內存中,其成本接近全表掃描兩個表的成本之和。如果表很大不能完全放入內存,這時優化器會將它分割成若干不同的分區,不能放入內存的部 分就把該分區寫入磁盤的臨時段,此時要有較大的臨時段從而儘量提高I/O 的性能。臨時段中的分區都需要換進內存做hash join。這時候成本接近於全表掃描小表+分區數*全表掃描大表的代價和。至於兩個表都進行分區,其好處是可以使用parallel query,就是多個進程同時對不同的分區進行join,然後再合併。但是複雜。
以下條件下hash join可能有優勢:
兩個巨大的表之間的連接。
在一個巨大的表和一個小表之間的連接。
可用ordered提示來改變CBO默認的驅動表,可用USE_HASH(table_name1 table_name2)提示來強制使用hash join。

3、SORT MERGE JOIN
sort merge join的操作通常分三步:對連接的每個表做table access full;對table access full的結果進行排序;進行merge join對排序結果進行合併。sort merge join性能開銷幾乎都在前兩步。一般是在沒有索引的情況下,9i開始已經很少出現了,因爲其排序成本高,大多爲hash join替代了。
通常情況下hash join的效果都比sort merge join要好,然而如果行源已經被排過序,在執行sort merge join時不需要再排序了,這時sort merge join的性能會優於hash join。
在全表掃描比索引範圍掃描再通過rowid進行表訪問更可取的情況下,sort merge join會比nested loops性能更佳。
可用USE_MERGE(table_name1 table_name2)提示強制使用sort merge join。

參考資料網址:1. http://www.cnblogs.com/zwl715/p/3788070.html
2. http://www.cnblogs.com/zwl715/p/3789042.html
1.3.4. 優化sql注意點
1、索引只能告訴你什麼存在於表中,而不能告訴你什麼不存在於表中。對於存在空值的列可以創建僞複合索引,具體如下:
某一張表mytable中含有三列 A\B\C,其中A列中含有NULL值
現在有索引 create index ind _a on mytable(A)
select * from mytable where a is null(is not null) 均不走索引
如果有僞複合索引 create index ind_a1 on mytable(A,0)
則:select * from mytable where a is null(is not null) 走索引
需要注意的是,當空值佔比很大時,這種方法是走不了索引的;
也可以建立nvl的函數索引:
CREATE INDEX ind _a ON mytable (NVL(A,0));
查詢時如下:
select * from mytable where a is nvl(A,0)=:P;
2、經常同時存取多列且每列都含有重複值可考慮建立組合索引。組合索引要儘量使關鍵查詢形成索引覆蓋,其前導列一定是使用最頻繁的列。
3、LIKE用於模糊檢索,LIKE檢索的樣式有三種:前匹配(XX%)、中間匹配(X%X)、後匹配(%XX)。對於前匹配可以使用索引,而使用中間匹配和後匹配,都不能使用索引。因此除非必要,否則應儘量避免使用中間匹配和後匹配。
4.使用hint的方式調整sql執行時的連接方式,索引使用,使之與另一個未加hint的sql一致,查詢效果也有可能不同,因爲索引的使用方式有可能不同。

共有五類不同的使用模式。

1。INDEX UNIQUE SCAN 效率最高,主鍵或唯一索引
2。INDEX FULL SCAN 有順序的輸出,不能並行讀索引
問題:如果表中建立了多個索引,Oracle是把所有的索引都掃描一遍麼?

3。INDEX FAST FULL SCAN 讀的最塊,可以並行訪問索引,但輸出不按順序
4。INDEX RANGE SCAN 給定的區間查詢
5。INDEX SKIP SCAN 聯合索引,不同值越少的列,越要放在前面
參考鏈接:http://www.cnblogs.com/tracy/archive/2011/09/02/2163462.html
5. 如果你自己能訪問DB服務器的話,直接在DB服務器上執行$ORACLE_HOME/rdbms/admin/awrrpt腳本就能生成
生成的時候會讓你選擇格式是html的還是txt得,會讓選擇要看哪個時間段的數據
html格式的可以很清晰地看到各種top10語句

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章