前些天,同事說一張空表查詢要花了大概40分鐘,DELETE的時候也是,在一些Q羣上問些有經驗的人說可能是高位線的問題.
於是讓他查下該表所佔的空間有多大.如圖:
一張空表居然佔了184704個塊,然後讓他把那表TRUNCATE後,再查就只有8個塊.這樣就更加的肯定是高水位的原因.
一個塊一般是8K,這樣的話,這張空表就佔了184704*8=1477632K=1443M.對1G多的空間進行全表掃描的話,可想而知需要花費的時間要很長.
下面是關於高水位的一些內容,參考:http://jolly10.itpub.net/post/7268/466758
一般ORACLE的邏輯存儲有4個粒度,塊,區,段,表空間.
其中,塊是最小的存儲單元.一般的塊所佔的空間一般是8K,但也有2K,4K,16K,32K這樣配置,具體通過參數db_block_size來設置.
2)區:由一系列相鄰的塊而組成,這也是ORACLE空間分配的基本單位,舉個例子來說,當我們創建一個表PM_USER時,首先ORACLE會分配一區的空間給這個表,隨着不斷的INSERT數據到PM_USER,原來的這個區容不下插入的數據時,ORACLE是以區爲單位進行擴展的,也就是說再分配多少個區給PM_USER,而不是多少個塊.
(3)段:是由一系列的區所組成,一般來說,當創建一個對象時(表,索引),就會分配一個段給這個對象.所以從某種意義上來說,段就是某種特定的數據.如CREATE TABLE PM_USER,這個段就是數據段,而CREATE INDEX ON PM_USER(NAME),ORACLE同樣會分配一個段給這個索引,但這是一個索引段了.查詢段的信息可以通過數據字典: SELECT * FROM USER_SEGMENTS來獲得,
(4)表空間:包含段,區及塊.表空間的數據物理上儲存在其所在的數據文件中.一個數據庫至少要有一個表空間.
OK,我們現在回到HWM上來,那麼,什麼是高水位標記呢?這就跟ORACLE的段空間管理相關了.
(一)ORACLE用HWM來界定一個段中使用的塊和未使用的塊.
舉個例子來說,當我們創建一個表:PT_SCHE_DETAIL時,ORACLE就會爲這個對象分配一個段.在這個段中,即使我們未插入任何記錄,也至少有一個區被分配,第一個區的第一個塊就稱爲段頭(SEGMENT HEADE),段頭中就儲存了一些信息,基中HWM的信息就存儲在此.此時,因爲第一個區的第一塊用於存儲段頭的一些信息,雖然沒有存儲任何實際的記錄,但也算是被使用,此時HWM是位於第2個塊.當我們不斷插入數據到PM_USER後,第1個塊已經放不下後面新插入的數據,此時,ORACLE將高水位之上的塊用於存儲新增數據,同時,HWM本身也向上移.也就是說,當我們不斷插入數據時,HWM會往不斷上移,這樣,在HWM之下的,就表示使用過的塊,HWM之上的就表示已分配但從未使用過的塊.
(二)HWM在插入數據時,當現有空間不足而進行空間的擴展時會向上移,但刪除數據時不會往下移.
這就好比是水庫的水位,當漲水時,水位往上移,當水退出後,最高水位的痕跡還是清淅可見.
考慮讓我們看一個段,如一張表,其中填滿了塊,如圖 1 所示。在正常操作過程中,刪除了一些行,如圖 2 所示。現有就有了許多浪費的空間:(I) 在表的上一個末端和現有的塊之間,以及 (II)在塊內部,其中還有一些沒有刪除的行。
圖" 1:分配給該表的塊。用灰色正方形表示行
ORACLE 不會釋放空間以供其他對象使用,有一條簡單的理由:由於空間是爲新插入的行保留的,並且要適應現有行的增長。被佔用的最高空間稱爲最高使用標記 (HWM),如圖 2 所示。
圖" 2:行後面的塊已經刪除了;HWM 仍保持不變
(三)HWM的信息存儲在段頭當中.
HWM本身的信息是儲存在段頭.在段空間是手工管理方式時,ORACLE是通過FREELIST(一個單向鏈表)來管理段內的空間分配.在段空間是自動管理方式時(ASSM),ORACLE是通過BITMAP來管理段內的空間分配.
(四)ORACLE的全表掃描是讀取高水位標記(HWM)以下的所有塊.
所以問題就產生了(一直不解爲何ORACLE會採用這種不合理的方式).當用戶發出一個全表掃描時,ORACLE 始終必須從段一直掃描到 HWM,即使它什麼也沒有發現。該任務延長了全表掃描的時間。
(五)當用直接路徑插入行時 — 例如,通過直接加載插入(用 APPEND 提示插入)或通過SQL*LOADER 直接路徑 — 數據塊直接置於 HWM 之上。它下面的空間就浪費掉了。
我們來分析這兩個問題,後者只是帶來空間的浪費,但前者不僅是空間的浪費,而且會帶來嚴重的性能問題.我們來看看下面的例子:
(A)我們先來搭建測試的環境,第一步先創建一個段空間爲手工管理的表空間:
CREATE TABLESPACE "RAINNY" LOGGING DATAFILE'D:ORACLE_HOMEORADATARAINNYRAINNY.ORA' SIZE 5M AUTOEXTEND ON NEXT 10MMAXSIZE UNLIMITED EXTENT MANAGEMENT LOCAL SEGMENT SPACE MANAGEMENTMANUAL;
(B)創建一個表,注意,此表的第二個字段我故意設成是CHAR(100),以讓此表在插入1千萬條記錄後,空間有足夠大:
CREATE TABLE TEST_TAB(C1 NUMBER(10),C2 CHAR(100)) TABLESPACE RAINNY;
插入記錄DECLARE
I NUMBER(10);BEGINFOR I IN 1..10000000 LOOPINSERT INTO TEST_TABVALUES(I,'TESTSTRING');END LOOP;COMMIT;END ;
(C)我們來查詢一下,看在插入一千萬條記錄後所訪問的塊數和查詢所用時間:
SQL> SET TIMING ON
SQL> SET AUTOTRACE TRACEONLY
SQL> SELECT COUNT(*) FROM TEST_TAB;
ELAPSED: 00:01:03.05
EXECUTION PLAN
----------------------------------------------------------
0 SELECT STATEMENT OPTIMIZER=CHOOSE (COST=15056 CARD=1)
1 0 SORT (AGGREGATE)
2 1 TABLE ACCESS (FULL) OF 'TEST_TAB' (COST=15056 CARD=10000
000)
STATISTICS
----------------------------------------------------------
0 RECURSIVE CALLS
0 DB BLOCK GETS
156310 CONSISTENT GETS
154239 PHYSICAL READS
0 REDO SIZE
379 BYTES SENT VIA SQL*NET TO CLIENT
503 BYTES RECEIVED VIA SQL*NET FROM CLIENT
2 SQL*NET ROUNDTRIPS TO/FROM CLIENT
0 SORTS (MEMORY)
0 SORTS (DISK)
1 ROWS PROCESSED
SQL>
我們來看上面的執行計劃,這句SQL總供耗時是:1分3秒.訪問方式是採用全表掃描方式(FTS),邏輯讀了156310個BLOCK,物理讀了154239個BLOCK.
我們來分析一下這個表:
BEGIN
DBMS_STATS.GATHER_TABLE_STATS(OWNNAME=> 'TEST',
TABNAME=> 'TEST_TAB',
PARTNAME=> NULL);END;
發現這個表目前使用的BLOCK有: 156532,未使用的BLOCK(EMPTY_BLOCKS)爲:0,總行數爲(NUM_ROWS):1000 0000
(D)接下來我們把此表的記錄用DELETE方式刪掉,然後再來看看SELECT COUNT(*) FROM TEST_TAB所花的時間:
DELETE FROM TEST_TAB;
COMMIT;
SQL> SELECT COUNT(*) FROM TEST_TAB;
ELAPSED: 00:01:04.03
EXECUTION PLAN
----------------------------------------------------------
0 SELECT STATEMENT OPTIMIZER=CHOOSE (COST=15056 CARD=1)
1 0 SORT (AGGREGATE)
2 1 TABLE ACCESS (FULL) OF 'TEST_TAB' (COST=15056 CARD=1)
STATISTICS
----------------------------------------------------------
0 RECURSIVE CALLS
0 DB BLOCK GETS
156310 CONSISTENT GETS
155565 PHYSICAL READS
0 REDO SIZE
378 BYTES SENT VIA SQL*NET TO CLIENT
503 BYTES RECEIVED VIA SQL*NET FROM CLIENT
2 SQL*NET ROUNDTRIPS TO/FROM CLIENT
0 SORTS (MEMORY)
0 SORTS (DISK)
1 ROWS PROCESSED
SQL>
大家來看,在DELETE表後,此時表中已沒有一條記錄,爲什麼SELECT COUNT(*) FROM TEST_TAB花的時間爲1分4秒,