數(shù)據(jù)庫(kù)優(yōu)化,應(yīng)該首先從大的方面考慮:網(wǎng)絡(luò)、服務(wù)器硬件配置、操作系統(tǒng)配置、Oracle服務(wù)器配置、數(shù)據(jù)結(jié)構(gòu)組織、然后才是具體的調(diào)整。實(shí)際上網(wǎng)絡(luò)、硬件等往往無(wú)法決定更換,應(yīng)用程序一般也無(wú)法修改,因此應(yīng)該著重從數(shù)據(jù)庫(kù)配置、數(shù)據(jù)結(jié)構(gòu)上來(lái)下手,首先讓數(shù)據(jù)庫(kù)有一個(gè)良好的配置,然后再考慮具體優(yōu)化某些過(guò)慢的語(yǔ)句。我在給我的用戶系統(tǒng)進(jìn)行優(yōu)化的過(guò)程中,總結(jié)了一些基本的,簡(jiǎn)單易行的辦法來(lái)優(yōu)化數(shù)據(jù)庫(kù)的方法.
一.設(shè)置合適的SGA
常常有人抱怨服務(wù)器硬件很好,但是Oracle就是很慢。很可能是內(nèi)存分配不合理造成的。
(1)假設(shè)內(nèi)存有512M,這通常是小型應(yīng)用。建議Oracle的SGA大約240M,其中:共享池(SHARED_POOL_SIZE)可以設(shè)置 60M到80M,根據(jù)實(shí)際的用戶數(shù)、查詢等來(lái)定。數(shù)據(jù)塊緩沖區(qū)可以大致分配120M-150M,8i下需要設(shè)置 DB_BLOCK_BUFFERS,DB_BLOCK_BUFFER*DB_BLOCK_SIZE等于數(shù)據(jù)塊緩沖區(qū)大小。9i 下的數(shù)據(jù)緩沖區(qū)可以用db_cache_size來(lái)直接分配。
(2)假設(shè)內(nèi)存有1G,Oracle 的SGA可以考慮分配500M:共享池分配100M到150M,數(shù)據(jù)緩沖區(qū)分配300M到400M。
(3)內(nèi)存2G,SGA可以考慮分配1.2G,共享池300M到500M,剩下的給數(shù)據(jù)塊緩沖區(qū)。
(4)內(nèi)存2G以上:共享池300M到500M就足夠啦,再多也沒(méi)有太大幫助;(Biti_rainy有專述)數(shù)據(jù)緩沖區(qū)是盡可能的大,但是一定要注意兩個(gè)問(wèn)題:一是要給操作系統(tǒng)和其他應(yīng)用留夠內(nèi)存,二是對(duì)于32位的操作系統(tǒng),Oracle的SGA有1.75G的限制。有的32位操作系統(tǒng)上可以突破這個(gè)限制,方法還請(qǐng)看Biti的大作吧。
二.分析表和索引,更改優(yōu)化模式
Oracle默認(rèn)優(yōu)化模式是CHOOSE,在這種情況下,如果表沒(méi)有經(jīng)過(guò)分析,經(jīng)常導(dǎo)致查詢使用全表掃描,而不使用索引。這通常導(dǎo)致磁盤I/O太多,而導(dǎo)致查詢很慢。如果沒(méi)有使用執(zhí)行計(jì)劃穩(wěn)定性,則應(yīng)該把表和索引都分析一下,這樣可能直接會(huì)使查詢速度大幅提升。分析表命令可以用ANALYZE TABLE 分析索引可以用ANALYZE INDEX命令。對(duì)于少于100萬(wàn)的表,可以考慮分析整個(gè)表,對(duì)于很大的表,可以按百分比來(lái)分析,但是百分比不能過(guò)低,否則生成的統(tǒng)計(jì)信息可能不準(zhǔn)確??梢酝ㄟ^(guò)DBA_TABLES的LAST_ANALYZED列來(lái)查看表是否經(jīng)過(guò)分析或分析時(shí)間,索引可以通過(guò)DBA_INDEXES的 LAST_ANALYZED列。
下面通過(guò)例子來(lái)說(shuō)明分析前后的速度對(duì)比。(表CASE_GA_AJZLZ大約有35萬(wàn)數(shù)據(jù),有主鍵)首先在SQLPLUS中打開自動(dòng)查詢執(zhí)行計(jì)劃功能。(第一次要執(zhí)行\(zhòng)RDBMS\ADMIN\utlxplan.sql來(lái)創(chuàng)建PLAN_TABLE這個(gè)表)
SQL> SET AUTOTRACE ON
SQL>SET TIMING ON
通過(guò)SET AUTOTRACE ON 來(lái)查看語(yǔ)句的執(zhí)行計(jì)劃,通過(guò)SET TIMING ON 來(lái)查看語(yǔ)句運(yùn)行時(shí)間。
SQL> select count(*) from CASE_GA_AJZLZ;
COUNT(*)
----------
346639
已用時(shí)間: 00: 00: 21.38
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE
1 0 SORT (AGGREGATE)
2 1 TABLE ACCESS (FULL) OF 'CASE_GA_AJZLZ'
……………………
請(qǐng)注意上面分析中的TABLE ACCESS(FULL),這說(shuō)明該語(yǔ)句執(zhí)行了全表掃描。而且查詢使用了21.38秒。這時(shí)表還沒(méi)有經(jīng)過(guò)分析。下面我們來(lái)對(duì)該表進(jìn)行分析:
SQL> analyze table CASE_GA_AJZLZ compute statistics;
表已分析。
已用時(shí)間: 00: 05: 357.63
然后再來(lái)查詢:
SQL> select count(*) from CASE_GA_AJZLZ;
COUNT(*)
----------
346639
已用時(shí)間: 00: 00: 00.71
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=FIRST_ROWS (Cost=351 Card=1)
1 0 SORT (AGGREGATE)
2 1 INDEX (FAST FULL SCAN) OF 'PK_AJZLZ' (UNIQUE) (Cost=351
Card=346351)
…………………………
請(qǐng)注意,這次時(shí)間僅僅用了0.71秒!這要?dú)w功于INDEX(FAST FULL SCAN)。通過(guò)分析表,查詢使用了PK_AJZLZ索引,磁盤I/O大幅減少,速度也大幅提升!下面的實(shí)用語(yǔ)句可以用來(lái)生成分析某個(gè)用戶的所有表和索引,假設(shè)用戶是GAXZUSR:
SQL> set pagesize 0
SQL> spool d:\analyze_tables.sql;
SQL> select 'analyze table '||owner||'.'||table_name||' compute statistics;' from dba_tables where owner='GAXZUSR';
SQL> spool off
SQL> spool spool d:\analyze_indexes.sql;
SQL> select 'analyze index '||owner||'.'||index_name||' compute statistics;' from dba_indexes where owner='GAXZUSR';
SQL> spool off
SQL> @d:\analyze_tables.sql
SQL> @d:\analyze_indexes.sql
解釋:上面的語(yǔ)句生成了兩個(gè)sql文件,分別分析全部的GAXZUSR的表和索引。如果需要按照百分比來(lái)分析表,可以修改一下腳本。通過(guò)上面的步驟,我們就完成了對(duì)表和索引的分析,可以測(cè)試一下速度的改進(jìn)啦。建議定期運(yùn)行上面的語(yǔ)句,尤其是數(shù)據(jù)經(jīng)過(guò)大量更新。
當(dāng)然,也可以通過(guò)dbms_stats來(lái)分析表和索引,更方便一些。但是我仍然習(xí)慣上面的方法,因?yàn)槌晒εc否會(huì)直接提示出來(lái)。
另外,我們可以將優(yōu)化模式進(jìn)行修改。optimizer_mode值可以是RULE、CHOOSE、FIRST_ROWS和ALL_ROWS。對(duì)于 OLTP系統(tǒng),可以改成FIRST_ROWS,來(lái)要求查詢盡快返回結(jié)果。這樣即使不用分析,在一般情況下也可以提高查詢性能。但是表和索引經(jīng)過(guò)分析后有助于找到最合適的執(zhí)行計(jì)劃。
三.設(shè)置cursor_sharing=FORCE 或SIMILAR
這種方法是8i才開始有的,oracle805不支持。通過(guò)設(shè)置該參數(shù),可以強(qiáng)制共享只有文字不同的語(yǔ)句解釋計(jì)劃。例如下面兩條語(yǔ)句可以共享:
SQL> SELECT * FROM MYTABLE WHERE NAME='tom'
SQL> SELECT * FROM MYTABLE WHERE NAME='turner'
這個(gè)方法可以大幅降低緩沖區(qū)利用率低的問(wèn)題,避免語(yǔ)句重新解釋。通過(guò)這個(gè)功能,可以很大程度上解決硬解析帶來(lái)的性能下降的問(wèn)題。個(gè)人感覺(jué)可根據(jù)系統(tǒng)的實(shí)際情況,決定是否將該參數(shù)改成FORCE。該參數(shù)默認(rèn)是exact。不過(guò)一定要注意,修改之前,必須先給ORACLE打補(bǔ)丁,否則改之后oracle會(huì)占用100%的CPU,無(wú)法使用。對(duì)于ORACLE9i,可以設(shè)置成SIMILAR,這個(gè)設(shè)置綜合了FORCE和EXACT的優(yōu)點(diǎn)。不過(guò)請(qǐng)慎用這個(gè)功能,這個(gè)參數(shù)也可能帶來(lái)很大的負(fù)面影響!
四.將常用的小表、索引釘在數(shù)據(jù)緩存KEEP池中
內(nèi)存上數(shù)據(jù)讀取速度遠(yuǎn)遠(yuǎn)比硬盤中讀取要快,據(jù)稱,內(nèi)存中數(shù)據(jù)讀的速度是硬盤的14000倍!如果資源比較豐富,把常用的小的、而且經(jīng)常進(jìn)行全表掃描的表給釘內(nèi)存中,當(dāng)然是在好不過(guò)了??梢院?jiǎn)單的通過(guò)ALTER TABLE tablename CACHE來(lái)實(shí)現(xiàn),在ORACLE8i之后可以使用ALTER TABLE table STORAGE(BUFFER_POOL KEEP)。一般來(lái)說(shuō),可以考慮把200數(shù)據(jù)塊之內(nèi)的表放在keep池中,當(dāng)然要根據(jù)內(nèi)存大小等因素來(lái)定。關(guān)于如何查出那些表或索引符合條件,可以使用本文提供的access.sql和access_report.sql。這兩個(gè)腳本是著名的Oracle專家 Burleson寫的,你也可以在讀懂了情況下根據(jù)實(shí)際情況調(diào)整一下腳本。對(duì)于索引,可以通過(guò)ALTER INDEX indexname STORAGE(BUFFER_POOL KEEP)來(lái)釘在KEEP池中。
將表定在KEEP池中需要做一些準(zhǔn)備工作。對(duì)于 ORACLE9i 需要設(shè)置DB_KEEP_CACHE_SIZE,對(duì)于8i,需要設(shè)置buffer_pool_keep。在8i中,還要修改 db_block_lru_latches,該參數(shù)默認(rèn)是1,無(wú)法使用buffer_pool_keep。該參數(shù)應(yīng)該比2*3*CPU數(shù)量少,但是要大于 1,才能設(shè)置DB_KEEP_CACHE_BUFFER。buffer_pool_keep從db_block_buffers中分配,因此也要小于 db_block_buffers。設(shè)置好這些參數(shù)后,就可以把常用對(duì)象永久釘在內(nèi)存里。
五.設(shè)置optimizer_max_permutations
對(duì)于多表連接查詢,如果采用基于成本優(yōu)化(CBO),ORACLE會(huì)計(jì)算出很多種運(yùn)行方案,從中選擇出最優(yōu)方案。這個(gè)參數(shù)就是設(shè)置oracle究竟從多少種方案來(lái)選擇最優(yōu)。如果設(shè)置太大,那么計(jì)算最優(yōu)方案過(guò)程也是時(shí)間比較長(zhǎng)的。Oracle805和8i默認(rèn)是80000,8建議改成2000。對(duì)于9i,已經(jīng)默認(rèn)是2000了。
六.調(diào)整排序參數(shù)
(1) SORT_AREA_SIZE:默認(rèn)的用來(lái)排序的SORT_AREA_SIZE大小是32K,通常顯得有點(diǎn)小,一般可以考慮設(shè)置成1M(1048576)。這個(gè)參數(shù)不能設(shè)置過(guò)大,因?yàn)槊總€(gè)連接都要分配同樣的排序內(nèi)存。
(2) SORT_MULTIBLOCK_READ_COUNT:增大這個(gè)參數(shù)可以提高臨時(shí)表空間排序性能,該參數(shù)默認(rèn)是2,可以改成32來(lái)對(duì)比一下排序查詢時(shí)間變化。注意,這個(gè)參數(shù)的最大值與平臺(tái)有關(guān)系。
七.調(diào)整其它幾個(gè)關(guān)鍵的性能參數(shù)
很多人認(rèn)為使用oracle數(shù)據(jù)庫(kù),系統(tǒng)的默認(rèn)參數(shù)就是最好的,其實(shí)不是這樣,很多參數(shù)都需要調(diào)整,而且調(diào)整前后性能大不一樣。
(1) log_buffer
日志緩沖區(qū)大小默認(rèn)設(shè)置32k太小了,建議設(shè)置成512K或者1M。
log_buffer=524288
(2) optimizer_index_caching
這個(gè)參數(shù)可以設(shè)置索引的緩沖度,范圍是0到100,默認(rèn)是0,可以考慮設(shè)置成90
(3) optimizer_index_cost_adj
這個(gè)參數(shù)是一個(gè)百分比,表明索引掃描與全表掃描的代價(jià)范圍是1到1000。默認(rèn)=100表名索引掃描與全表掃描代價(jià)一樣。將這個(gè)參數(shù)設(shè)小表名索引代價(jià)要小于全表掃描,這樣就使得使用CBO進(jìn)行成本計(jì)算時(shí)更傾向于使用索引掃描。建議把這個(gè)參數(shù)設(shè)置成30到50。
八.改變聯(lián)機(jī)日志文件大小(一般用于oracle805)
Oracle805的聯(lián)機(jī)日志文件默認(rèn)只有1M大小,這實(shí)在是太小了,通過(guò)查看數(shù)據(jù)庫(kù)的日志,很可能發(fā)現(xiàn)“checkpoint not complete”之類的錯(cuò)誤提示。這會(huì)導(dǎo)致系統(tǒng)穩(wěn)定性,同樣也降低了數(shù)據(jù)庫(kù)性能。建議修改成10M。修改方法是刪除一個(gè)組、添加一個(gè)組,直到3個(gè)組都換成新的大小。說(shuō)明:這個(gè)操作需要實(shí)施人員具有較多的數(shù)據(jù)庫(kù)知識(shí),如果不太了解,最好不要試驗(yàn)。
九.改變數(shù)據(jù)塊大小(一般用于oracle805)
Oracle805默認(rèn)的塊(DB_BLOCK_SIZE)大小是2K,太小了,因?yàn)閴K小,所以請(qǐng)求同樣的數(shù)據(jù)量的時(shí)候,讀的次數(shù)就要增多,導(dǎo)致性能低下。當(dāng)然如果服務(wù)器性能比較好,還是升級(jí)Oracle更好,如果服務(wù)器配置比較差,建議改成8K。但是數(shù)據(jù)塊不能直接修改,唯一的辦法就是將數(shù)據(jù)導(dǎo)出,重新創(chuàng)建數(shù)據(jù)庫(kù),然后將數(shù)據(jù)導(dǎo)入。說(shuō)明:這個(gè)操作需要實(shí)施人員具有較多的數(shù)據(jù)庫(kù)知識(shí),如果不太了解,最好不要試驗(yàn)。
十.設(shè)置合適的表存儲(chǔ)參數(shù)
對(duì)于有很多并發(fā)寫入用戶的系統(tǒng)來(lái)說(shuō),如果系統(tǒng)沒(méi)有經(jīng)過(guò)調(diào)整,經(jīng)常會(huì)有數(shù)據(jù)等待現(xiàn)象。這是因?yàn)?i之前的表設(shè)置的默認(rèn)的自由隊(duì)列freelists為1,這樣就可能造成數(shù)據(jù)等待。通過(guò)查看v$waitstat,如果發(fā)現(xiàn)data block 或者free list類的count次數(shù)很大,則說(shuō)明等待情況嚴(yán)重,需要增加freelists。這個(gè)參數(shù)在8i、9i中可以動(dòng)態(tài)修改(需要打補(bǔ)丁,否則會(huì)有ORA- 10620: Operation not allowed on this segment)在ORACLE805中,只能通過(guò)重新創(chuàng)建表來(lái)修改。
SQL> select * from v$waitstat;
CLASS COUNT TIME
------------------ ---------- ----------
data block 11922013 342456
sort block 0 0
save undo block 0 0
segment header 1 0
free list 0 0
如果測(cè)算經(jīng)常有10個(gè)并發(fā)的寫用戶,可以把表的freelists改成10。例如下面的腳本可以把GAXZUSR用戶的所有表重新設(shè)置FREELISTS的語(yǔ)句寫在D:\FREELISTS.SQL里:
SQL> SET PAGESIZE 0
SQL> SPOOL D:\FREELISTS.SQL
SQL> SELECT 'ALTER TABLE '||TABLE_NAME||' STORAGE(FREELISTS 10);' FROM DBA_TABLES WHERE OWNER=’GAXZUSR’;
SQL>SPOOL OFF
檢查D:\FREELISTS.SQL,沒(méi)有錯(cuò)誤后運(yùn)行修改FREELISTS:
SQL>@D:\FREELISTS.SQL
十一.重新組織表結(jié)構(gòu)
(1) 按照主鍵重新排序。
數(shù)據(jù)庫(kù)運(yùn)行了一段時(shí)間后,可能會(huì)有很多數(shù)據(jù),而這些數(shù)據(jù)又可能是經(jīng)常按照某個(gè)字段來(lái)選取區(qū)段數(shù)據(jù)。如果我們能夠把主鍵按照順序重新來(lái)組織一下表,那么用主鍵進(jìn)行的查詢就會(huì)明顯快很多,主要是因?yàn)榻?jīng)過(guò)排序后,相似的編號(hào)都放在同一個(gè)數(shù)據(jù)塊里,ORACLE在進(jìn)行主鍵范圍查找的時(shí)候,就會(huì)大大減少物理塊度讀取數(shù)量。在對(duì)表和索引分析之后,可以通過(guò)DBA_INDEXES的CLUSTERING_FACTOR列來(lái)判斷表是否需要重新排序。如果該字段的值與表的 BLOCK數(shù)量差不多,則不需要重新排序,如果和表的行數(shù)差不多,則應(yīng)該考慮重新組織一下了。下面的例子示意性說(shuō)明怎樣對(duì)表CASE_GA_AJZLZ按照主鍵PK_AJZLZ進(jìn)行重新排序:
<1> 將表CASE_GA_AJZLZ的索引、外間約束引用等找出來(lái)備用。
SET PAGESIZE 0
SET LINESIZE 300
SPOOL DISABLE_CONSTRAINTS.SQL
SELECT 'ALTER TABLE '||TABLE_NAME||' DISABLE CONSTRAINT '||CONSTRAINT_NAME||';' FROM USER_CONSTRAINTS WHERE CONSTRAINT_TYPE='R' AND R_CONSTRAINT_NAME='PK_AJZLZ';
SPOOL OFF
SPOOL CREATE_CONSTRAINTS.SQL
SELECT 'ALTER TABLE '||TABLE_NAME||' ADD CONSTRAINT '||CONSTRAINT_NAME||' FOREIGN KEY(CASEID) REFERENCES CASE_GA_AJZLZ(CASEID);' FROM USER_CONSTRAINTS WHERE CONSTRAINT_TYPE='R' AND R_CONSTRAINT_NAME='PK_AJZLZ';
SPOOL OFF
SPOOL CREATE_INDEX.SQL
SELECT 'CREATE INDEX '||INDEX_NAME||' ON '||TABLE_NAME||'('||COLUMN_NAME||') TABLESPACE INDX ;' FROM USER_IND_COLUMNS WHERE TABLE_NAME='CASE_GA_AJZLZ' AND INDEX_NAME<>'PK_AJZLZ';
<2> 創(chuàng)建新的表CASE_GA_AJZLZ_NEW:
SQL> CREATE TABLE CASE_GA_AJZLZ_NEW AS SELECT
/*+INDEX(CASE_GA_AJZLZ PK_AJZLZ) */ * FROM CASE_GA_AJZLZ ;
注意,上面的注釋(紅顏色部分)表名按照PK_AJZLZ排序來(lái)重新組織表。
<3>禁用CASE_GA_AJZLZ的外間約束,將表CASE_GA_AJZLZ TRUNCATE,然后刪除之
<4> 將表CASE_GA_AJZLZ_NEW更名為CASE_GA_AJZLZ
SQL> ALTER TABLE CASE_GA_AJZLZ_NEW RENAME TO CASE_GA_AJZLZ;
<5>創(chuàng)建CASE_GA_AJZLZ的所有索引、主鍵約束等。
SQL> ALTER TABLE CASE_GA_AJZLZ ADD CONSTRAINT PK_AJZLZ PRIMARY KEY(CASEID);
SQL> @CREATE_INDEX.SQL
SQL> @CREATE_CONSTRAINTS.SQL
(2) 將BLOB字段存儲(chǔ)到單獨(dú)的表空間中。
基本上每個(gè)業(yè)務(wù)系統(tǒng)都有很多BLOB字段,而且很可能占據(jù)了整個(gè)數(shù)據(jù)庫(kù)大小的大部分。默認(rèn)情況下,BLOB字段會(huì)將4000個(gè)字節(jié)的指針與表的行存在一起,這直接會(huì)導(dǎo)致行遷移。而且BLOB字段會(huì)與表處于同一個(gè)表空間,這也對(duì)性能有不小的影響。從設(shè)計(jì)角度來(lái)說(shuō),BLOB字段都應(yīng)該單獨(dú)存儲(chǔ),遺憾的是我所遇到的很多系統(tǒng)都沒(méi)有單獨(dú)存儲(chǔ)BLOB字段。如果BLOB字段占據(jù)了很大的存儲(chǔ),那么將BLOB字段單獨(dú)存儲(chǔ)后,帶來(lái)的整體性能收益可能會(huì)非常的大。 另外BLOB字段存儲(chǔ)子句中有一個(gè)DISABLE STORAGE IN ROW 屬性,在將BLOB字段單獨(dú)存放時(shí),也應(yīng)該實(shí)用該屬性,這樣可以有效避免行遷移。
一.設(shè)置合適的SGA
常常有人抱怨服務(wù)器硬件很好,但是Oracle就是很慢。很可能是內(nèi)存分配不合理造成的。
(1)假設(shè)內(nèi)存有512M,這通常是小型應(yīng)用。建議Oracle的SGA大約240M,其中:共享池(SHARED_POOL_SIZE)可以設(shè)置 60M到80M,根據(jù)實(shí)際的用戶數(shù)、查詢等來(lái)定。數(shù)據(jù)塊緩沖區(qū)可以大致分配120M-150M,8i下需要設(shè)置 DB_BLOCK_BUFFERS,DB_BLOCK_BUFFER*DB_BLOCK_SIZE等于數(shù)據(jù)塊緩沖區(qū)大小。9i 下的數(shù)據(jù)緩沖區(qū)可以用db_cache_size來(lái)直接分配。
(2)假設(shè)內(nèi)存有1G,Oracle 的SGA可以考慮分配500M:共享池分配100M到150M,數(shù)據(jù)緩沖區(qū)分配300M到400M。
(3)內(nèi)存2G,SGA可以考慮分配1.2G,共享池300M到500M,剩下的給數(shù)據(jù)塊緩沖區(qū)。
(4)內(nèi)存2G以上:共享池300M到500M就足夠啦,再多也沒(méi)有太大幫助;(Biti_rainy有專述)數(shù)據(jù)緩沖區(qū)是盡可能的大,但是一定要注意兩個(gè)問(wèn)題:一是要給操作系統(tǒng)和其他應(yīng)用留夠內(nèi)存,二是對(duì)于32位的操作系統(tǒng),Oracle的SGA有1.75G的限制。有的32位操作系統(tǒng)上可以突破這個(gè)限制,方法還請(qǐng)看Biti的大作吧。
二.分析表和索引,更改優(yōu)化模式
Oracle默認(rèn)優(yōu)化模式是CHOOSE,在這種情況下,如果表沒(méi)有經(jīng)過(guò)分析,經(jīng)常導(dǎo)致查詢使用全表掃描,而不使用索引。這通常導(dǎo)致磁盤I/O太多,而導(dǎo)致查詢很慢。如果沒(méi)有使用執(zhí)行計(jì)劃穩(wěn)定性,則應(yīng)該把表和索引都分析一下,這樣可能直接會(huì)使查詢速度大幅提升。分析表命令可以用ANALYZE TABLE 分析索引可以用ANALYZE INDEX命令。對(duì)于少于100萬(wàn)的表,可以考慮分析整個(gè)表,對(duì)于很大的表,可以按百分比來(lái)分析,但是百分比不能過(guò)低,否則生成的統(tǒng)計(jì)信息可能不準(zhǔn)確??梢酝ㄟ^(guò)DBA_TABLES的LAST_ANALYZED列來(lái)查看表是否經(jīng)過(guò)分析或分析時(shí)間,索引可以通過(guò)DBA_INDEXES的 LAST_ANALYZED列。
下面通過(guò)例子來(lái)說(shuō)明分析前后的速度對(duì)比。(表CASE_GA_AJZLZ大約有35萬(wàn)數(shù)據(jù),有主鍵)首先在SQLPLUS中打開自動(dòng)查詢執(zhí)行計(jì)劃功能。(第一次要執(zhí)行\(zhòng)RDBMS\ADMIN\utlxplan.sql來(lái)創(chuàng)建PLAN_TABLE這個(gè)表)
SQL> SET AUTOTRACE ON
SQL>SET TIMING ON
通過(guò)SET AUTOTRACE ON 來(lái)查看語(yǔ)句的執(zhí)行計(jì)劃,通過(guò)SET TIMING ON 來(lái)查看語(yǔ)句運(yùn)行時(shí)間。
SQL> select count(*) from CASE_GA_AJZLZ;
COUNT(*)
----------
346639
已用時(shí)間: 00: 00: 21.38
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE
1 0 SORT (AGGREGATE)
2 1 TABLE ACCESS (FULL) OF 'CASE_GA_AJZLZ'
……………………
請(qǐng)注意上面分析中的TABLE ACCESS(FULL),這說(shuō)明該語(yǔ)句執(zhí)行了全表掃描。而且查詢使用了21.38秒。這時(shí)表還沒(méi)有經(jīng)過(guò)分析。下面我們來(lái)對(duì)該表進(jìn)行分析:
SQL> analyze table CASE_GA_AJZLZ compute statistics;
表已分析。
已用時(shí)間: 00: 05: 357.63
然后再來(lái)查詢:
SQL> select count(*) from CASE_GA_AJZLZ;
COUNT(*)
----------
346639
已用時(shí)間: 00: 00: 00.71
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=FIRST_ROWS (Cost=351 Card=1)
1 0 SORT (AGGREGATE)
2 1 INDEX (FAST FULL SCAN) OF 'PK_AJZLZ' (UNIQUE) (Cost=351
Card=346351)
…………………………
請(qǐng)注意,這次時(shí)間僅僅用了0.71秒!這要?dú)w功于INDEX(FAST FULL SCAN)。通過(guò)分析表,查詢使用了PK_AJZLZ索引,磁盤I/O大幅減少,速度也大幅提升!下面的實(shí)用語(yǔ)句可以用來(lái)生成分析某個(gè)用戶的所有表和索引,假設(shè)用戶是GAXZUSR:
SQL> set pagesize 0
SQL> spool d:\analyze_tables.sql;
SQL> select 'analyze table '||owner||'.'||table_name||' compute statistics;' from dba_tables where owner='GAXZUSR';
SQL> spool off
SQL> spool spool d:\analyze_indexes.sql;
SQL> select 'analyze index '||owner||'.'||index_name||' compute statistics;' from dba_indexes where owner='GAXZUSR';
SQL> spool off
SQL> @d:\analyze_tables.sql
SQL> @d:\analyze_indexes.sql
解釋:上面的語(yǔ)句生成了兩個(gè)sql文件,分別分析全部的GAXZUSR的表和索引。如果需要按照百分比來(lái)分析表,可以修改一下腳本。通過(guò)上面的步驟,我們就完成了對(duì)表和索引的分析,可以測(cè)試一下速度的改進(jìn)啦。建議定期運(yùn)行上面的語(yǔ)句,尤其是數(shù)據(jù)經(jīng)過(guò)大量更新。
當(dāng)然,也可以通過(guò)dbms_stats來(lái)分析表和索引,更方便一些。但是我仍然習(xí)慣上面的方法,因?yàn)槌晒εc否會(huì)直接提示出來(lái)。
另外,我們可以將優(yōu)化模式進(jìn)行修改。optimizer_mode值可以是RULE、CHOOSE、FIRST_ROWS和ALL_ROWS。對(duì)于 OLTP系統(tǒng),可以改成FIRST_ROWS,來(lái)要求查詢盡快返回結(jié)果。這樣即使不用分析,在一般情況下也可以提高查詢性能。但是表和索引經(jīng)過(guò)分析后有助于找到最合適的執(zhí)行計(jì)劃。
三.設(shè)置cursor_sharing=FORCE 或SIMILAR
這種方法是8i才開始有的,oracle805不支持。通過(guò)設(shè)置該參數(shù),可以強(qiáng)制共享只有文字不同的語(yǔ)句解釋計(jì)劃。例如下面兩條語(yǔ)句可以共享:
SQL> SELECT * FROM MYTABLE WHERE NAME='tom'
SQL> SELECT * FROM MYTABLE WHERE NAME='turner'
這個(gè)方法可以大幅降低緩沖區(qū)利用率低的問(wèn)題,避免語(yǔ)句重新解釋。通過(guò)這個(gè)功能,可以很大程度上解決硬解析帶來(lái)的性能下降的問(wèn)題。個(gè)人感覺(jué)可根據(jù)系統(tǒng)的實(shí)際情況,決定是否將該參數(shù)改成FORCE。該參數(shù)默認(rèn)是exact。不過(guò)一定要注意,修改之前,必須先給ORACLE打補(bǔ)丁,否則改之后oracle會(huì)占用100%的CPU,無(wú)法使用。對(duì)于ORACLE9i,可以設(shè)置成SIMILAR,這個(gè)設(shè)置綜合了FORCE和EXACT的優(yōu)點(diǎn)。不過(guò)請(qǐng)慎用這個(gè)功能,這個(gè)參數(shù)也可能帶來(lái)很大的負(fù)面影響!
四.將常用的小表、索引釘在數(shù)據(jù)緩存KEEP池中
內(nèi)存上數(shù)據(jù)讀取速度遠(yuǎn)遠(yuǎn)比硬盤中讀取要快,據(jù)稱,內(nèi)存中數(shù)據(jù)讀的速度是硬盤的14000倍!如果資源比較豐富,把常用的小的、而且經(jīng)常進(jìn)行全表掃描的表給釘內(nèi)存中,當(dāng)然是在好不過(guò)了??梢院?jiǎn)單的通過(guò)ALTER TABLE tablename CACHE來(lái)實(shí)現(xiàn),在ORACLE8i之后可以使用ALTER TABLE table STORAGE(BUFFER_POOL KEEP)。一般來(lái)說(shuō),可以考慮把200數(shù)據(jù)塊之內(nèi)的表放在keep池中,當(dāng)然要根據(jù)內(nèi)存大小等因素來(lái)定。關(guān)于如何查出那些表或索引符合條件,可以使用本文提供的access.sql和access_report.sql。這兩個(gè)腳本是著名的Oracle專家 Burleson寫的,你也可以在讀懂了情況下根據(jù)實(shí)際情況調(diào)整一下腳本。對(duì)于索引,可以通過(guò)ALTER INDEX indexname STORAGE(BUFFER_POOL KEEP)來(lái)釘在KEEP池中。
將表定在KEEP池中需要做一些準(zhǔn)備工作。對(duì)于 ORACLE9i 需要設(shè)置DB_KEEP_CACHE_SIZE,對(duì)于8i,需要設(shè)置buffer_pool_keep。在8i中,還要修改 db_block_lru_latches,該參數(shù)默認(rèn)是1,無(wú)法使用buffer_pool_keep。該參數(shù)應(yīng)該比2*3*CPU數(shù)量少,但是要大于 1,才能設(shè)置DB_KEEP_CACHE_BUFFER。buffer_pool_keep從db_block_buffers中分配,因此也要小于 db_block_buffers。設(shè)置好這些參數(shù)后,就可以把常用對(duì)象永久釘在內(nèi)存里。
五.設(shè)置optimizer_max_permutations
對(duì)于多表連接查詢,如果采用基于成本優(yōu)化(CBO),ORACLE會(huì)計(jì)算出很多種運(yùn)行方案,從中選擇出最優(yōu)方案。這個(gè)參數(shù)就是設(shè)置oracle究竟從多少種方案來(lái)選擇最優(yōu)。如果設(shè)置太大,那么計(jì)算最優(yōu)方案過(guò)程也是時(shí)間比較長(zhǎng)的。Oracle805和8i默認(rèn)是80000,8建議改成2000。對(duì)于9i,已經(jīng)默認(rèn)是2000了。
六.調(diào)整排序參數(shù)
(1) SORT_AREA_SIZE:默認(rèn)的用來(lái)排序的SORT_AREA_SIZE大小是32K,通常顯得有點(diǎn)小,一般可以考慮設(shè)置成1M(1048576)。這個(gè)參數(shù)不能設(shè)置過(guò)大,因?yàn)槊總€(gè)連接都要分配同樣的排序內(nèi)存。
(2) SORT_MULTIBLOCK_READ_COUNT:增大這個(gè)參數(shù)可以提高臨時(shí)表空間排序性能,該參數(shù)默認(rèn)是2,可以改成32來(lái)對(duì)比一下排序查詢時(shí)間變化。注意,這個(gè)參數(shù)的最大值與平臺(tái)有關(guān)系。
七.調(diào)整其它幾個(gè)關(guān)鍵的性能參數(shù)
很多人認(rèn)為使用oracle數(shù)據(jù)庫(kù),系統(tǒng)的默認(rèn)參數(shù)就是最好的,其實(shí)不是這樣,很多參數(shù)都需要調(diào)整,而且調(diào)整前后性能大不一樣。
(1) log_buffer
日志緩沖區(qū)大小默認(rèn)設(shè)置32k太小了,建議設(shè)置成512K或者1M。
log_buffer=524288
(2) optimizer_index_caching
這個(gè)參數(shù)可以設(shè)置索引的緩沖度,范圍是0到100,默認(rèn)是0,可以考慮設(shè)置成90
(3) optimizer_index_cost_adj
這個(gè)參數(shù)是一個(gè)百分比,表明索引掃描與全表掃描的代價(jià)范圍是1到1000。默認(rèn)=100表名索引掃描與全表掃描代價(jià)一樣。將這個(gè)參數(shù)設(shè)小表名索引代價(jià)要小于全表掃描,這樣就使得使用CBO進(jìn)行成本計(jì)算時(shí)更傾向于使用索引掃描。建議把這個(gè)參數(shù)設(shè)置成30到50。
八.改變聯(lián)機(jī)日志文件大小(一般用于oracle805)
Oracle805的聯(lián)機(jī)日志文件默認(rèn)只有1M大小,這實(shí)在是太小了,通過(guò)查看數(shù)據(jù)庫(kù)的日志,很可能發(fā)現(xiàn)“checkpoint not complete”之類的錯(cuò)誤提示。這會(huì)導(dǎo)致系統(tǒng)穩(wěn)定性,同樣也降低了數(shù)據(jù)庫(kù)性能。建議修改成10M。修改方法是刪除一個(gè)組、添加一個(gè)組,直到3個(gè)組都換成新的大小。說(shuō)明:這個(gè)操作需要實(shí)施人員具有較多的數(shù)據(jù)庫(kù)知識(shí),如果不太了解,最好不要試驗(yàn)。
九.改變數(shù)據(jù)塊大小(一般用于oracle805)
Oracle805默認(rèn)的塊(DB_BLOCK_SIZE)大小是2K,太小了,因?yàn)閴K小,所以請(qǐng)求同樣的數(shù)據(jù)量的時(shí)候,讀的次數(shù)就要增多,導(dǎo)致性能低下。當(dāng)然如果服務(wù)器性能比較好,還是升級(jí)Oracle更好,如果服務(wù)器配置比較差,建議改成8K。但是數(shù)據(jù)塊不能直接修改,唯一的辦法就是將數(shù)據(jù)導(dǎo)出,重新創(chuàng)建數(shù)據(jù)庫(kù),然后將數(shù)據(jù)導(dǎo)入。說(shuō)明:這個(gè)操作需要實(shí)施人員具有較多的數(shù)據(jù)庫(kù)知識(shí),如果不太了解,最好不要試驗(yàn)。
十.設(shè)置合適的表存儲(chǔ)參數(shù)
對(duì)于有很多并發(fā)寫入用戶的系統(tǒng)來(lái)說(shuō),如果系統(tǒng)沒(méi)有經(jīng)過(guò)調(diào)整,經(jīng)常會(huì)有數(shù)據(jù)等待現(xiàn)象。這是因?yàn)?i之前的表設(shè)置的默認(rèn)的自由隊(duì)列freelists為1,這樣就可能造成數(shù)據(jù)等待。通過(guò)查看v$waitstat,如果發(fā)現(xiàn)data block 或者free list類的count次數(shù)很大,則說(shuō)明等待情況嚴(yán)重,需要增加freelists。這個(gè)參數(shù)在8i、9i中可以動(dòng)態(tài)修改(需要打補(bǔ)丁,否則會(huì)有ORA- 10620: Operation not allowed on this segment)在ORACLE805中,只能通過(guò)重新創(chuàng)建表來(lái)修改。
SQL> select * from v$waitstat;
CLASS COUNT TIME
------------------ ---------- ----------
data block 11922013 342456
sort block 0 0
save undo block 0 0
segment header 1 0
free list 0 0
如果測(cè)算經(jīng)常有10個(gè)并發(fā)的寫用戶,可以把表的freelists改成10。例如下面的腳本可以把GAXZUSR用戶的所有表重新設(shè)置FREELISTS的語(yǔ)句寫在D:\FREELISTS.SQL里:
SQL> SET PAGESIZE 0
SQL> SPOOL D:\FREELISTS.SQL
SQL> SELECT 'ALTER TABLE '||TABLE_NAME||' STORAGE(FREELISTS 10);' FROM DBA_TABLES WHERE OWNER=’GAXZUSR’;
SQL>SPOOL OFF
檢查D:\FREELISTS.SQL,沒(méi)有錯(cuò)誤后運(yùn)行修改FREELISTS:
SQL>@D:\FREELISTS.SQL
十一.重新組織表結(jié)構(gòu)
(1) 按照主鍵重新排序。
數(shù)據(jù)庫(kù)運(yùn)行了一段時(shí)間后,可能會(huì)有很多數(shù)據(jù),而這些數(shù)據(jù)又可能是經(jīng)常按照某個(gè)字段來(lái)選取區(qū)段數(shù)據(jù)。如果我們能夠把主鍵按照順序重新來(lái)組織一下表,那么用主鍵進(jìn)行的查詢就會(huì)明顯快很多,主要是因?yàn)榻?jīng)過(guò)排序后,相似的編號(hào)都放在同一個(gè)數(shù)據(jù)塊里,ORACLE在進(jìn)行主鍵范圍查找的時(shí)候,就會(huì)大大減少物理塊度讀取數(shù)量。在對(duì)表和索引分析之后,可以通過(guò)DBA_INDEXES的CLUSTERING_FACTOR列來(lái)判斷表是否需要重新排序。如果該字段的值與表的 BLOCK數(shù)量差不多,則不需要重新排序,如果和表的行數(shù)差不多,則應(yīng)該考慮重新組織一下了。下面的例子示意性說(shuō)明怎樣對(duì)表CASE_GA_AJZLZ按照主鍵PK_AJZLZ進(jìn)行重新排序:
<1> 將表CASE_GA_AJZLZ的索引、外間約束引用等找出來(lái)備用。
SET PAGESIZE 0
SET LINESIZE 300
SPOOL DISABLE_CONSTRAINTS.SQL
SELECT 'ALTER TABLE '||TABLE_NAME||' DISABLE CONSTRAINT '||CONSTRAINT_NAME||';' FROM USER_CONSTRAINTS WHERE CONSTRAINT_TYPE='R' AND R_CONSTRAINT_NAME='PK_AJZLZ';
SPOOL OFF
SPOOL CREATE_CONSTRAINTS.SQL
SELECT 'ALTER TABLE '||TABLE_NAME||' ADD CONSTRAINT '||CONSTRAINT_NAME||' FOREIGN KEY(CASEID) REFERENCES CASE_GA_AJZLZ(CASEID);' FROM USER_CONSTRAINTS WHERE CONSTRAINT_TYPE='R' AND R_CONSTRAINT_NAME='PK_AJZLZ';
SPOOL OFF
SPOOL CREATE_INDEX.SQL
SELECT 'CREATE INDEX '||INDEX_NAME||' ON '||TABLE_NAME||'('||COLUMN_NAME||') TABLESPACE INDX ;' FROM USER_IND_COLUMNS WHERE TABLE_NAME='CASE_GA_AJZLZ' AND INDEX_NAME<>'PK_AJZLZ';
<2> 創(chuàng)建新的表CASE_GA_AJZLZ_NEW:
SQL> CREATE TABLE CASE_GA_AJZLZ_NEW AS SELECT
/*+INDEX(CASE_GA_AJZLZ PK_AJZLZ) */ * FROM CASE_GA_AJZLZ ;
注意,上面的注釋(紅顏色部分)表名按照PK_AJZLZ排序來(lái)重新組織表。
<3>禁用CASE_GA_AJZLZ的外間約束,將表CASE_GA_AJZLZ TRUNCATE,然后刪除之
<4> 將表CASE_GA_AJZLZ_NEW更名為CASE_GA_AJZLZ
SQL> ALTER TABLE CASE_GA_AJZLZ_NEW RENAME TO CASE_GA_AJZLZ;
<5>創(chuàng)建CASE_GA_AJZLZ的所有索引、主鍵約束等。
SQL> ALTER TABLE CASE_GA_AJZLZ ADD CONSTRAINT PK_AJZLZ PRIMARY KEY(CASEID);
SQL> @CREATE_INDEX.SQL
SQL> @CREATE_CONSTRAINTS.SQL
(2) 將BLOB字段存儲(chǔ)到單獨(dú)的表空間中。
基本上每個(gè)業(yè)務(wù)系統(tǒng)都有很多BLOB字段,而且很可能占據(jù)了整個(gè)數(shù)據(jù)庫(kù)大小的大部分。默認(rèn)情況下,BLOB字段會(huì)將4000個(gè)字節(jié)的指針與表的行存在一起,這直接會(huì)導(dǎo)致行遷移。而且BLOB字段會(huì)與表處于同一個(gè)表空間,這也對(duì)性能有不小的影響。從設(shè)計(jì)角度來(lái)說(shuō),BLOB字段都應(yīng)該單獨(dú)存儲(chǔ),遺憾的是我所遇到的很多系統(tǒng)都沒(méi)有單獨(dú)存儲(chǔ)BLOB字段。如果BLOB字段占據(jù)了很大的存儲(chǔ),那么將BLOB字段單獨(dú)存儲(chǔ)后,帶來(lái)的整體性能收益可能會(huì)非常的大。 另外BLOB字段存儲(chǔ)子句中有一個(gè)DISABLE STORAGE IN ROW 屬性,在將BLOB字段單獨(dú)存放時(shí),也應(yīng)該實(shí)用該屬性,這樣可以有效避免行遷移。