高水位問題

前些天,同事說一張空表查詢要花了大概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的段空間管理相關了.

()ORACLEHWM來界定一個段中使用的塊和未使用的塊.

舉個例子來說,當我們創建一個表: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 5AUTOEXTEND 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
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總供耗時是:13.訪問方式是採用全表掃描方式(FTS),讀了156310BLOCK,物理讀了154239BLOCK.

我們來分析一下這個表:

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花的時間爲14,




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