一,沒有優化的速度:Executed in 69.436 seconds
drop table t purge;
create table t(x int);
/*清空共享池,注意在生產環境中千萬不能做這步操作*/
alter system flush shared_pool;
create or replace procedure proc1
as
begin
for i in 1 .. 100000
loop
execute immediate
'insert into t values('||i||')';
commit;
end loop;
end;
/
下面查看下proc1插入100000記錄的執行時間
SQL> set timing on;
SQL> exec proc1;
PL/SQL procedure successfully completed
Executed in 69.436 seconds
/*我們可以通過下面的語句查看此存儲過程執行的具體步驟*/
select t.sql_text,t.sql_id,t.parse_calls,t.executions from v$sql t where sql_text like '%insert into t values%';
為了方便查看我用PL/SQL DEVELOPER 執行的上面語句,如下圖:
從上面可以看出,每個語句都只是解析了一次,執行了一次,一共解析了10萬次,也許你會問你上面只有7136行記錄啊,你怎么說是解析了10萬次呢。我可以告訴你肯定是解析了10萬次,因為我的共享池空間不大,容納不小10萬條信息,根據FIFO 的原理你可以看出,現在我查出來的都是從92000多開始的SQL STATEMENT記錄。我們知道這些SQL語句都是相似的沒有必要解析10萬次,即每一條語句都解析一次。這個PROC1 沒有用綁定變量,這就是我們可以優化的地方。我們用綁定變量來重新測試下,下面的PROC2就只用解析一次就可以了,當然速度肯定會提高不少。
二,使用綁定變量優化后的速度:Executed in 26.505 seconds
drop table t purge;
create table t(x int);
/*清空共享池,注意在生產環境中千萬不能做這步操作*/
alter system flush shared_pool;
create or replace procedure proc2
as
begin
for i in 1 .. 100000
loop
execute immediate
'insert into t values(:x)' using i;
commit;
end loop;
end;
/
SQL> set timing on;
SQL> exec proc2;
PL/SQL procedure successfully completed
Executed in 26.505 seconds
從上面可以看出,時間基本上減少了一半。
/*我們可以通過下面的語句查看此存儲過程執行的具體步驟*/
select t.sql_text,t.sql_id,t.parse_calls,t.executions from v$sql t where sql_text like '%insert into t values%' order by 1;
從上面的執行情況可以知道,解析了一次,執行了10萬次。完全符合我們的猜想,所以速度大大提升了。
execute immediate是一種動態SQL的寫法,常用於表名字段名是變量,入參的情況,由於表名不知道,所以不能直接寫SQL ,所以要靠動態SQL語句傳人表名和字段名參數拼接成SQLSTATEMENT,有execute immediate調用執行。但是我的這個例子完全可以不需要動態的,可以用靜態的寫好。
三,用靜態改寫后的速度:Executed in 19.391 seconds
drop table t purge;
create table t(x int);
/*清空共享池,注意在生產環境中千萬不能做這步操作*/
alter system flush shared_pool;
create or replace procedure proc3
as
begin
for i in 1 .. 100000
loop
insert into t values(i);
commit;
end loop;
end;
/
SQL> set timing on;
SQL> exec proc3;
PL/SQL procedure successfully completed
Executed in 19.391 seconds
從上面可以看出,proc3也實現了綁定變量,而且動態的特點是執行過程中再解析,而靜態的SQL的特點是編譯的過程是解析好的,所以上面的PRARSE_CALLS是0。注意這個和上面一個圖比較,上面的時PARSE_CALLS 是1,而這個是0,所以靜態的少了一個執行的時候的解析過程。
我們可以看出上面的三個PROC都是一條語句就commit一次,我們完全沒有必要這樣做,我們可以一起提交。如下例: commit的時把log_buffer里的信息通過LGWR寫到online redo log里,觸發LGWR寫10萬次,而且我們知道LGWR寫的太頻繁了。
四,批量提交的速度:Executed in 11.42 seconds
drop table t purge;
create table t(x int);
/*清空共享池,注意在生產環境中千萬不能做這步操作*/
alter system flush shared_pool;
create or replace procedure proc4
as
begin
for i in 1 .. 100000
loop
insert into t values(i);
end loop;
commit;
end;
/
SQL> set timing on;
SQL> exec proc4;
PL/SQL procedure successfully completed
Executed in 11.42 seconds
可以看出我們用的時間更少了。
五,集合寫法的速度:Executed in 0.452 seconds
drop table t purge;
create table t(x int);
/*清空共享池,注意在生產環境中千萬不能做這步操作*/
alter system flush shared_pool;
/*下面的語句是由上面的一條一條的插入改為一整批的寫進data buffer區里,所以比上面的快,批處理肯定比一個一個的執行快*/
insert into t select rownum from dual connect by level<=100000;
SQL> set timing on;
SQL> insert into t select rownum from dual connect by level<=100000;
100000 rows inserted
Executed in 0.452 seconds
這個是上面的前四種都是一條一條的插入的,我這個集合寫法是一整批地寫進到DATA BUFFER里,所以比上面的四種情況要快的多。
六,用直接路徑寫法速度(100萬條記錄):Executed in 1.514 seconds
/*下面用直接路徑的方式來操作,速度會比上面更快,所謂直接路徑就是數據不經過database buffer,而是直接寫到磁盤,少了一步寫到數據緩沖區(database buffer)的動作*/
drop table t purge;
alter system flush shared_pool;
SQL> set timing on;
SQL> create table t as select rownum x from dual connect by level<=1000000;
Table created
Executed in 1.514 seconds
注意此時我插入的記錄數十上面的10倍,我是插入100萬條記錄只用了1.514 seconds.
注意:直接路徑的寫法比集合寫法快事因為,insert into select .... 的方式是將數據首先寫到data buffer里,然后再刷到磁盤里。而create as t 的方式跳過了數據緩沖區(data buffer), 直接寫進磁盤中,這種方式稱之為直接路徑讀寫方式。本來是先到內存,在到磁盤,更改為直接到磁盤,少了一個步驟,所以速度快了。
七,並行寫法的速度(100萬條記錄):Executed in 0.733 seconds
/*並行加直接路徑,而且是不寫日志的,所以速度比上面的更快*/
drop table t purge;
alter system flush shared_pool;
set timing on;
create table t nologging parallel 64 as select rownum x from dual connect by level<=100000;
SQL> set timing on;
SQL> create table t nologging parallel 4 as select rownum x from dual connect by level<=1000000;
Table created
Executed in 0.733 seconds
我上面只用了parallel 4,如果更多的話,還會更快!!!
[z]
三個提高Oracle處理大量數據效率的有效途徑
Oracle性能話題涉及面非常廣,市場上有很多書籍專門介紹Oracle調優。對性能的追求是無止境的,需要長期不懈的努力;但要避免性能成為問題卻不難,甚至可以說很簡單。本文從簡單實用的角度出發,給出幾個提高Oracle處理大量數據效率的有效途徑。
一、配置數據庫初始化參數
數據庫的全部初始化參數可以在OEM中看到。參見下圖:
在新建一數據庫時,如果不配置這些初始化參數,Oracle會給這些參數以默認值。當數據庫規模不大時,采用Oracle的默認值通常不會遇到性能問題。下面介紹對處理大量數據效率有“舉足輕重”影響,並且,默認值會帶來性能問題的幾個參數:
1) db_block_size
該參數設置了Oracle進行一次I/O的基本單位——數據庫塊的大小 (以字節計)。毫不誇張的說,該參數對於大數據量處理是最重要的一個參數。該參數值設置的越大,對大數據量處理越有利。受操作系統所限,NT4最大只能設置為8K,Win2k最大只能設置為16K。Oracle本身允許的最大值是64K。忘了說了,db_block_size應設置為2的冪。
對於分析型數據庫,設置為32K是不錯的選擇。對於OLTP系統,筆者沒有多少經驗,網上的說法是設置8K是個比較好的平衡點。Oracle是有許多“神話”的,8K的說法未必符合現在的軟硬件情況,更未必符合我們企業的實際情況。如果有時間有機會,比較一下8K好還是16K好總是不會錯的。
db_block_size是最基本的一個參數,也是最容易被忽視的一個參數。該參數只能在創建數據庫時設置,此后不能更改;一旦有所失誤,只能通過重建數據庫的方法補救。因此,您建庫時應當慎重考慮該參數。
2)
db_file_multiblock_read_count
Oracle官方的說明:在涉及一個完全連續掃描的一次 I/O 操作過程中讀取的塊的最大數量。對於大的查詢來說,進行全表掃描往往比使用索引效率高很多。全表掃描操作是典型的“完全連續掃描”。如果db_block_size設置為32K,db_file_multiblock_read_count設置為8;則一次I/O操作最多可以連續讀8個數據庫塊,即256K。
db_file_multiblock_read_count並非越大越好。對於數據分析系統,db_file_multiblock_read_count和db_block_size的乘積為256K足夠了。對於建立在Unix上的OLTP系統,根據網上的說法,二者的乘積為64K是不錯的選擇。
根據筆者的經驗,讓數據連續分布在物理磁盤上比考量該參數更加有效。
3) sort_area_size
sort_area_size的重要性可以說是和db_block_size並列的。該參數指出數據庫執行一個查詢時最多可以使用多大內存來排序。受系統資源所限,我們無法將該參數設置太大。特別是當我們采用獨立模式建庫時,每個Session都可能會申請一個或多個排序空間。如果我們設置sort_area_size為8M,同時登上來100個用戶並發查詢,則可能會占去800M內存甚至更多。當主存不夠用時,就要用虛擬內存了。如果Oracle被迫使用虛擬內存,則數據庫的性能將急劇下降。
對於該參數的設置,網上有人建議至少應超過用於排序記錄數的平方根。也就是說,對100萬條記錄進行排序,每條記錄占用1K空間,則sort_area_size至少應設置為1M。對1000萬條記錄進行排序,每條記錄占用1K空間,sort_area_size設置為4M應該夠用了。
根據上述數字,OLTP系統的sort_area_size不妨設置為1M或2M;數據分析系統的sort_area_size不妨設置為4M或8M。
db_file_multiblock_read_count和sort_area_size在數據庫建立好以后是可以修改的。修改方法很簡單。搜索Oracle的安裝目錄,找到PFILE文件夾(可能會找到多個,其父目錄的名字會給我們提示),里面有一個init文本文件,照着里面的內容修改就可以了(找不到相關參數就自己加一個)。修改完畢后重啟數據庫方能生效。Oracle9i以上版本可以做到不用重啟數據庫,本文就不介紹了。
二、編寫高效的SQL
一般說來,看起來簡單的SQL通常都不會遇到性能問題。SQL的執行效率通常比程序的執行效率要高。因此,盡量用SQL解決問題和盡量用簡單的SQL解決問題應當是我們開發的指導原則。
編寫高效的SQL需要一定的基本功。本文不討論SQL的理論基礎。本文僅介紹一個有用的技術:人為干預SQL的執行計划。當SQL較復雜時,執行計划的可能性會非常多。用最短的時間選擇一個最優的執行計划是Oracle奮斗的目標。Oracle數據庫有相關的參數來調整挑選執行計划的算法,這些參數本文不討論,有興趣的讀者可以自己上網去搜。
所謂“人為干預SQL的執行計划”實際上是提示Oracle如何去挑選最優的執行計划。SQL提示的語法很簡單:用“/*+”和“*/”將提示包括起來,中間寫上關鍵字就可以了。SQL:提示的關鍵字有很多,下面介紹幾個典型的關鍵字,更多的用法可以自己上網搜。
1)指定全表掃描:
SELECT /*+FULL(table_name)*/ field1,field2
FROM table_name;
一個大查詢如果用到了一個大表中相當一部分的數據,則采用全表掃描的執行計划會比采用索引的執行計划效率高很多。
2)數據直接插入到表的最后,可以提高速度:
INSERT /*+append*/
INTO table_name
select * from table_name1;
Oracle中很多數據塊因為曾經做過delete操作而有空閑空間,如果使用append關鍵字,則Oracle不會去尋找這些有空閑空間的數據塊,從而提高了insert語句的執行速度。需注意append關鍵字只適合於大數據量插入。
三、分區
分區技術相對前面介紹的技術而言要復雜一些。分區實際上是據根據某(些)個字段在物理上將一個大表的數據分開存儲,從而,能提高我們查詢的效率,同時也能加強我們對數據的管理。典型例子的是根據日期字段分區,從而,當我們查詢某個時期的數據時,只需要掃描某個分區的數據而不需要掃描整表的數據。
當我們決定采用分區技術時,只需要在create table語句以及create index語句中增加一些語法。一般的面向DBA的書籍都會有專門的章節介紹分區技術。這里不再贅述。
一個表是否被分區並不影響我們使用:對普通表的操作可以用在分區表上。相反,分區表增加了我們使用該表的靈活性,創建分區表后,我們可以使用Alter table命令來增加、刪除、交換、移動、修改、重命名、划分、截斷一個已存在分區的結構。一個典型的例子,如果用月份分區,我們可以使用truncate命令在瞬間刪除某個月份的全部數據。
我們需要注意的是在創建局部唯一索引時,索引字段應包括分區字段,否則會創建失敗。創建全局索引則沒有這樣的限制。
