###4.4 選項數據庫
####4.4.1 惟一索引oracle
確保每一個索引值都是惟一的.app
主鍵、惟一鍵與惟一索引的區別dom
通常,咱們看到術語「索引」和「鍵」交換使用,但實際上這兩個是不一樣的。索引是存儲在數據庫中的一個物理結構,鍵純粹是一個邏輯概念。鍵表明建立來實施業務規則的完整性約束。索引和鍵的混淆一般是因爲數據庫使用索引來實施完整性約束。ide
主鍵約束、惟一索引的區別:函數
SQL> create table test(id int, name varchar2(20), constraint pk_id primary key(id)); Table created. SQL> select constraint_name, constraint_type from user_constraints where table_name = 'TEST'; CONSTRAINT_NAME C ------------------------------ - PK_ID P #在test表中,咱們指定了ID列做爲主鍵,Oracle數據庫會自動建立一個同名的惟一索引: SQL> select index_name, index_type,uniqueness from user_indexes where table_name = 'TEST'; INDEX_NAME INDEX_TYPE UNIQUENES -------------------- -------------------- --------- PK_ID NORMAL UNIQUE #此時,若是咱們再試圖在ID列上建立一個惟一索引,Oracle會報錯,由於該列上已經存在一個惟一索引: SQL> create unique index ind_test_uk on test(id); create unique index ind_test_uk on test(id) * ERROR at line 1: ORA-01408: such column list already indexed SQL> create index ind_test_uk on test(id); create index ind_test_uk on test(id) * ERROR at line 1: ORA-01408: such column list already indexed
惟一鍵約束、惟一索引的區別:oop
SQL> drop table test purge; Table dropped. SQL> create table test( 2 id int, 3 name varchar2(20), 4 constraint uk_test unique(id)); Table created. SQL> select constraint_name, constraint_type from user_constraints where table_name = 'TEST'; CONSTRAINT_NAME C ------------------------------ - UK_TEST U SQL> select index_name, index_type, uniqueness 2 from user_indexes 3 where table_name = 'TEST'; INDEX_NAME INDEX_TYPE UNIQUENES -------------------- -------------------- --------- UK_TEST NORMAL UNIQUE #Oracle一樣自動建立了一個同名的惟一索引,並且也不容許再在此列上建立惟一索引或非惟一索引。
主鍵約束要求列值非空(NOT NULL),那麼惟一鍵約束是否也要求非空呢?性能
SQL> insert into test values(1, 'sally'); 1 row created. SQL> insert into test values(null, 'Tony'); 1 row created. SQL> commit; Commit complete. SQL> select * from test; ID NAME ---------- -------------------- 1 sally Tony #從實驗結果來看, 惟一鍵約束並無非空要求。 惟一索引對列值非空不作要求。
鍵約束或者惟一鍵約束失效,Oracle自動建立的惟一索引是否會受到影響?測試
SQL> drop table test purge; Table dropped. create table test( id int, name varchar2(20), constraint uk_test unique(id)); Table created. SQL> select index_name, index_type, uniqueness, status from user_indexes where table_name = 'TEST'; INDEX_NAME INDEX_TYPE UNIQUENES STATUS -------------------- -------------------- --------- -------- UK_TEST NORMAL UNIQUE VALID SQL> alter table test disable constraint uk_test; Table altered. SQL> select index_name, index_type, uniqueness, status from user_indexes where table_name = 'TEST'; no rows selected SQL> alter table test enable constraint uk_test; Table altered. SQL> select index_name, index_type, uniqueness, status from user_indexes where table_name = 'TEST'; INDEX_NAME INDEX_TYPE UNIQUENES STATUS -------------------- -------------------- --------- -------- UK_TEST NORMAL UNIQUE VALID #當主鍵約束或者惟一鍵約束失效時,Oracle會標記隱式建立的惟一索引爲刪除狀態。
先建立惟一索引,再建立主鍵或者惟一鍵約束,狀況又會怎樣呢?fetch
SQL> drop table test purge; Table dropped. SQL> create table test( 2 id int, 3 name varchar(20)); Table created. SQL> create unique index idx_test_id on test(id); Index created. SQL> select index_name, index_type, uniqueness, status 2 from user_indexes 3 where table_name = 'TEST'; INDEX_NAME INDEX_TYPE UNIQUENES STATUS -------------------- -------------------- --------- -------- IDX_TEST_ID NORMAL UNIQUE VALID SQL> alter table test add constraint uk_test unique(id); Table altered. SQL> select index_name, index_type, uniqueness, status 2 from user_indexes 3 where table_name = 'TEST'; INDEX_NAME INDEX_TYPE UNIQUENES STATUS -------------------- -------------------- --------- -------- IDX_TEST_ID NORMAL UNIQUE VALID SQL> select constraint_name, constraint_type 2 from user_constraints 3 where table_name = 'TEST'; CONSTRAINT_NAME C ------------------------------ - UK_TEST U SQL> alter table test disable constraint uk_test; Table altered. SQL> select constraint_name, constraint_type, status 2 from user_constraints 3 where table_name = 'TEST'; CONSTRAINT_NAME C STATUS ------------------------------ - -------- UK_TEST U DISABLED SQL> select index_name, index_type, uniqueness, status 2 from user_indexes 3 where table_name = 'TEST'; INDEX_NAME INDEX_TYPE UNIQUENES STATUS -------------------- -------------------- --------- -------- IDX_TEST_ID NORMAL UNIQUE VALID
####4.4.2 反向關鍵字索引
「反向關鍵字索引」會按相反順序存儲索引值的字節。這能夠減小索引中特定熱點的活 動量。若是許多用戶正按同一順序處理數據,那麼在任何給定時刻,關鍵字值的前綴部分 (當前正在處理的)是很是接近的值。所以,在索引結構的該區域中會發生大量的活動。 爲反向字節樣式的關鍵字值創建索引後,反向關鍵字索引可在索引結構中分散這些活動。
REVERSE索引也是一種B樹索引,但它物理上將按照列順序保存的每一個索引鍵值進行了反轉。例如,索引鍵是20,用16進制存儲這個標準B樹索引鍵的兩個字節是C1,15,那麼反向索引存儲的字節就是15,C1。
反向索引主要解決的是葉子塊的爭用問題。在RAC中,這個問題更加明顯,可能多實例反覆修改同一個塊。舉個例子,在一張按照主鍵順序存儲的表中,一個實例增長記錄20,另外一個增長21,這兩個值的鍵存儲於同一個索引葉子塊的左右兩側。
在反向索引中,插入操做會被反序字節排列分發到索引的所有葉子鍵中。就像上面的例子,20和21兩個鍵,在標準鍵索引中,他們應該是相鄰的,但在反向索引中,他們會被分開存儲。所以按順序鍵插入操做的IO會更加平均。
由於索引上的數據不是按照列存儲的順序,反向索引會禁止一些案例中能夠用到的索引範圍掃描。例如,若是一個用戶查詢ID值大於20的記錄,那麼數據庫不能從包含這個ID的塊開始查找,而是從全部的葉子塊。
這種索引的設計目的是消除插入操做的索引熱點。對於插入的性能提高有幫助,但也是有限的,由於數據庫不能使用索引範圍掃描了。
Sometimes, using a reverse-key index can make an OLTP Oracle Real Application Clusters application faster. For example, keeping the index of mail messages in an e-mail application: some users keep old messages, and the index must maintain pointers to these as well as to the most recent.
SQL> create table t( 2 a number, 3 b varchar(10), 4 c date ); Table created. SQL> begin 2 for i int 1..1000 loop 3 insert into t values(i, 'Test', sysdate); 4 end loop; 5 commit; 6 end; 7 / SQL> create index ind_t_rev on t(a,b,c) reverse; Index created. SQL> select index_name, index_type from user_indexes where table_name = 'T'; INDEX_NAME INDEX_TYPE ------------------------------ --------------------------- IND_T_REV NORMAL/REV #Using the reverse key arrangement eliminates the ability to run an index range scanning query on the index. Because lexically adjacent keys are not stored next to each other in a reverse-key index, only fetch-by-key or full-index (table) scans can be performed. SQL> select * from t where a = 1000; ------------------------------------------------------------------------------ | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ------------------------------------------------------------------------------ | 0 | SELECT STATEMENT | | 1 | 29 | 2 (0)| 00:00:01 | |* 1 | INDEX RANGE SCAN| IND_T_REV | 1 | 29 | 2 (0)| 00:00:01 | ------------------------------------------------------------------------------ SQL> select count(*) from t where trunc(c) = to_date('24-JUL-15','DD-MM-YY'); --------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | --------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 9 | 3 (0)| 00:00:01 | | 1 | SORT AGGREGATE | | 1 | 9 | | | |* 2 | TABLE ACCESS FULL| T | 1000 | 9000 | 3 (0)| 00:00:01 | --------------------------------------------------------------------------- #取消reverse SQL> alter index ind_t_rev rebuild noreverse; Index altered. SQL> select index_name, index_type from user_indexes where table_name = 'T'; INDEX_NAME INDEX_TYPE ------------------------------ --------------------------- IND_T_REV NORMAL
####4.4.3 組合索引
在Oracle中能夠建立組合索引,即同時包含兩個或兩個以上列的索引。
- 當使用基於規則的優化器(RBO)時,只有當組合索引的前導列出如今SQL語句的where子句中時,纔會使用到該索引;
- 在使用Oracle9i以前的基於成本的優化器(CBO)時, 只有當組合索引的前導列出如今SQL語句的where子句中時,纔可能會使用到該索引,這取決於優化器計算的使用索引的成本和使用全表掃描的成本,Oracle會自動選擇成本低的訪問路徑(請見下面的測試1和測試2);
- 從Oracle9i起,Oracle引入了一種新的索引掃描方式——索引跳躍掃描(index skip scan),這種掃描方式只有基於成本的優化器(CBO)才能使用。這樣,當SQL語句的where子句中即便沒有組合索引的前導列,而且索引跳躍掃描的成本低於其餘掃描方式的成本時,Oracle就會使用該方式掃描組合索引(請見下面的測試3);
- Oracle優化器有時會作出錯誤的選擇,由於它再「聰明」,也不如咱們SQL語句編寫人員更清楚表中數據的分佈,在這種狀況下,經過使用提示(hint),咱們能夠幫助Oracle優化器做出更好的選擇(請見下面的測試4)。
建立測試表T
#T表建立 SQL> create table t as select * from all_objects; Table created. #數據分佈 SQL> select object_type, count(*) from t group by object_type; OBJECT_TYPE COUNT(*) ------------------- ---------- EDITION 1 INDEX PARTITION 512 TABLE SUBPARTITION 32 CONSUMER GROUP 2 SEQUENCE 245 SYNONYM 27889 JOB 15 ...... SQL> select count(*) from t; COUNT(*) ---------- 74051 #建立複合索引 SQL> create index indx_t on t(object_type,object_name); Index created. SQL> select INDEX_NAME, INDEX_type from user_indexes where table_name = 'T'; INDEX_NAME INDEX_TYPE --------------- --------------- INDX_T NORMAL SQL> select index_name, table_name, column_name from user_ind_columns where TABLE_NAME = 'T'; INDEX_NAME TABLE_NAME COLUMN_NAME --------------- --------------- ------------------------------ INDX_T T OBJECT_TYPE INDX_T T OBJECT_NAME SQL> analyze table t compute statistics 2 for table 3 for all indexes 4 for all indexed columns 5 / Table analyzed.
測試一:使用了組合索引的前導列而且訪問了表中的少許記錄
SQL> set autotrace traceonly SQL> select * from t where object_type='JOB'; 15 rows selected. Execution Plan ---------------------------------------------------------- Plan hash value: 723869532 -------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | -------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 15 | 1500 | 14 (0)| 00:00:01 | | 1 | TABLE ACCESS BY INDEX ROWID| T | 15 | 1500 | 14 (0)| 00:00:01 | |* 2 | INDEX RANGE SCAN | INDX_T | 15 | | 3 (0)| 00:00:01 | -------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 2 - access("OBJECT_TYPE"='JOB') Statistics ---------------------------------------------------------- 0 recursive calls 0 db block gets 16 consistent gets 0 physical reads 0 redo size 2980 bytes sent via SQL*Net to client 524 bytes received via SQL*Net from client 2 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 15 rows processed #正如咱們所指望的,因爲使用了組合索引的前導列而且訪問了表中的少許記錄,Oracle明智地選擇了索引掃描。
測試二:使用了組合索引的前導列,是因爲訪問了表中的大量數據
SQL> select * from t where object_type='SYNONYM'; 27889 rows selected. Execution Plan ---------------------------------------------------------- Plan hash value: 1601196873 -------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | -------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 27889 | 2723K| 297 (1)| 00:00:04 | |* 1 | TABLE ACCESS FULL| T | 27889 | 2723K| 297 (1)| 00:00:04 | -------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 1 - filter("OBJECT_TYPE"='SYNONYM') Statistics ---------------------------------------------------------- 1 recursive calls 0 db block gets 2894 consistent gets 0 physical reads 0 redo size 1381826 bytes sent via SQL*Net to client 20973 bytes received via SQL*Net from client 1861 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 27889 rows processed # 很明顯,即便使用了組合索引的前導列,可是因爲訪問了表中的大量數據,Oracle選擇了不使用索引而直接使用全表掃描,由於優化器認爲全表掃描的成本更低,但事實是否是真的這樣的?咱們經過增長提示(hint)來強制它使用索引來看看: SQL> select /*+ index(T indx_t) */ * from t where object_type = 'SYNONYM'; 27889 rows selected. Execution Plan ---------------------------------------------------------- Plan hash value: 723869532 -------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | -------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 27889 | 2723K| 20012 (1)| 00:04:01 | | 1 | TABLE ACCESS BY INDEX ROWID| T | 27889 | 2723K| 20012 (1)| 00:04:01 | |* 2 | INDEX RANGE SCAN | INDX_T | 27889 | | 173 (0)| 00:00:03 | -------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 2 - access("OBJECT_TYPE"='SYNONYM') Statistics ---------------------------------------------------------- 0 recursive calls 0 db block gets 24661 consistent gets 0 physical reads 0 redo size 3236139 bytes sent via SQL*Net to client 20973 bytes received via SQL*Net from client 1861 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 27889 rows processed #從以上結果能夠看出,在訪問大量數據的狀況下,使用索引確實會致使更高的執行成本,這從statistics部分的邏輯讀取數(consistent gets)就能夠看出,使用索引致使的邏輯讀取數是不使用索引致使的邏輯讀的10倍還多。所以,Oracle明智地選擇了全表掃描而不是索引掃描。
測試三: where子句中沒有索引前導列的狀況
SQL> select * from t where object_name = 'DBA_TAB_COLS'; Execution Plan ---------------------------------------------------------- Plan hash value: 2722864248 -------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | -------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 2 | 200 | 43 (0)| 00:00:01 | | 1 | TABLE ACCESS BY INDEX ROWID| T | 2 | 200 | 43 (0)| 00:00:01 | |* 2 | INDEX SKIP SCAN | INDX_T | 2 | | 41 (0)| 00:00:01 | -------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 2 - access("OBJECT_NAME"='DBA_TAB_COLS') filter("OBJECT_NAME"='DBA_TAB_COLS') Statistics ---------------------------------------------------------- 1 recursive calls 0 db block gets 35 consistent gets 0 physical reads 0 redo size 1753 bytes sent via SQL*Net to client 524 bytes received via SQL*Net from client 2 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 2 rows processed 因爲只查詢了2條數據,即便沒有使用前導列,Oracle正確地選擇了索引跳躍掃描。咱們再來看看若是不使用索引跳躍掃描,該語句的成本: SQL> select /*+ no_index(t indx_t)*/ * from t where object_name = 'DBA_TAB_COLS'; Execution Plan ---------------------------------------------------------- Plan hash value: 1601196873 -------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | -------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 2 | 200 | 296 (1)| 00:00:04 | |* 1 | TABLE ACCESS FULL| T | 2 | 200 | 296 (1)| 00:00:04 | -------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 1 - filter("OBJECT_NAME"='DBA_TAB_COLS') Statistics ---------------------------------------------------------- 1 recursive calls 0 db block gets 1060 consistent gets 0 physical reads 0 redo size 1747 bytes sent via SQL*Net to client 524 bytes received via SQL*Net from client 2 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 2 rows processed
測試四:不選擇使用索引的狀況
SQL> select * from t where object_name like 'DE%'; 101 rows selected. Execution Plan ---------------------------------------------------------- Plan hash value: 1601196873 -------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | -------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 267 | 26700 | 296 (1)| 00:00:04 | |* 1 | TABLE ACCESS FULL| T | 267 | 26700 | 296 (1)| 00:00:04 | -------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 1 - filter("OBJECT_NAME" LIKE 'DE%') Statistics ---------------------------------------------------------- 1 recursive calls 0 db block gets 1065 consistent gets 0 physical reads 0 redo size 8012 bytes sent via SQL*Net to client 590 bytes received via SQL*Net from client 8 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 101 rows processed #此次只選擇了101條數據,跟表T中總的數據量74051條相比,顯然只是很小的一部分,可是Oracle仍是選擇了全表掃描,有1065個邏輯讀。這種狀況下,若是咱們強制使用索引,狀況會怎樣呢? SQL> select /*+ index(t indx_t)*/ * from t where object_name like 'DE%'; 101 rows selected. Execution Plan ---------------------------------------------------------- Plan hash value: 2722864248 -------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | -------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 267 | 26700 | 455 (0)| 00:00:06 | | 1 | TABLE ACCESS BY INDEX ROWID| T | 267 | 26700 | 455 (0)| 00:00:06 | |* 2 | INDEX SKIP SCAN | INDX_T | 267 | | 265 (0)| 00:00:04 | -------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 2 - access("OBJECT_NAME" LIKE 'DE%') filter("OBJECT_NAME" LIKE 'DE%') Statistics ---------------------------------------------------------- 1 recursive calls 0 db block gets 119 consistent gets 0 physical reads 0 redo size 11862 bytes sent via SQL*Net to client 590 bytes received via SQL*Net from client 8 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 101 rows processed #經過添加提示(hint),咱們強制Oracle使用了索引掃描(INDEX SKIP SCAN),執行了119個邏輯讀,比使用全表掃描的時候少。 #因而可知,Oracle優化器有時會作出錯誤的選擇,由於它再「聰明」,也不如咱們SQL語句編寫人員更清楚表中數據的分佈,在這種狀況下,經過使用提示(hint),咱們能夠幫助Oracle優化器做出更好的選擇。
####4.4.4 基於函數的索引
使用Oracle函數索引,無疑是提升查詢效率的有效方法之一。談到任何對列的操做均可能致使全表掃描,例如:
SQL> select employee_id, first_name from employees where substr(first_name,1,2) = 'Sa'; ------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 11 | 3 (0)| 00:00:01 | |* 1 | TABLE ACCESS FULL| EMPLOYEES | 1 | 11 | 3 (0)| 00:00:01 | -------------------------------------------------------------------------------
可是這種查詢在客服系統又常用,咱們能夠建立一個帶有substr函數的基於Oracle函數索引,
create index emp_fname_substr on employees(substr(first_name, 1, 2)); SQL> select index_name, index_type from user_indexes where table_name = 'EMPLOYEES'; INDEX_NAME INDEX_TYPE ------------------------------ --------------------------- EMP_LAST_NAME_IDX NORMAL EMP_PHONE_IX NORMAL EMP_FNAME_SUBSTR FUNCTION-BASED NORMAL SQL> select employee_id, first_name from employees where substr(first_name,1,2) = 'Sa'; ------------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 14 | 2 (0)| 00:00:01 | | 1 | TABLE ACCESS BY INDEX ROWID| EMPLOYEES | 1 | 14 | 2 (0)| 00:00:01 | |* 2 | INDEX RANGE SCAN | EMP_FNAME_SUBSTR | 1 | | 1 (0)| 00:00:01 | -------------------------------------------------------------------------------------------
這樣在執行上面的查詢語句時,這個基於函數的索引將排上用場,執行計劃將是(INDEX RANGE SCAN)。 上面的例子中,咱們建立了基於函數的索引,可是若是執行下面的查詢:
SQL> select employee_id, first_name from employees where substr(first_name,1,1) = 'S'; 13 rows selected. Execution Plan ---------------------------------------------------------- Plan hash value: 1445457117 ------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 11 | 3 (0)| 00:00:01 | |* 1 | TABLE ACCESS FULL| EMPLOYEES | 1 | 11 | 3 (0)| 00:00:01 | -------------------------------------------------------------------------------
獲得的執行計劃將仍是(TABLE ACCESS FULL),由於只有當數據列可以等式匹配時,基於函數的索引才能生效,這樣對於這種索引的計劃和維護的要求都很高。請注意,向表中添加索引是很是危險的操做,由於這將致使許多查詢執行計劃的變動。然而,若是咱們使用基於函數的索引就不會產生這樣的問題,由於Oracle只有在查詢使用了匹配的內置函數時纔會使用這種類型的索引。
####4.4.5 壓縮索引
oracle 索引壓縮(key compression)是oracle 9i 中引入的一項新特性。該特性能夠壓縮索引或者索引組織表中的重複鍵值,從而節省存儲空間。非分區的unique 索引和non-unique(至少兩列)索引都可以被壓縮。bitmap 索引不可以進行壓縮。
在oracle 索引壓縮中有幾個比較糾結的術語,須要說明一下。索引壓縮是經過將索引中的鍵值拆分紅兩部分實現的,也就是grouping piece 也稱做prefix 和 unique piece 也稱做suffix 。grouping piece 是用來壓縮的被unique piece 共享的部分。若是鍵值不能提供unique piece,那麼oracle 將會使用rowid 來惟一標識。只有B-tree 索引的葉子節點可以被壓縮,分支節點不可以被壓縮。索引壓縮是在單個block 中完成的,不可以跨blocks進行索引壓縮。grouping piece (prefix) 和 unique piece (suffix) 存儲在同一個索引 block 中。
具體prefix 和 suffix 是怎麼劃分的呢?默認prefix 長度等於索引列的數量減去1。固然咱們能夠人爲控制prefix 的長度,非惟一索引的最大prefix 長度等於索引列的數量。惟一索引的最大prefix 長度等於索引列的數量減去1。好比,假設索引有三個列: 默認的時候:prefix (column1,column2) suffix (column3) 若是有如下幾組鍵值(1,2,3),(1,2,4),(1,2,7),(1,3,5),(1,3,4),(1,4,4) 那麼在prefix中重複的(1,2),(1,3) 將會被壓縮至保留一份。
索引壓縮適合於那些鍵值重複率高的索引,這樣纔可以達到壓縮鍵值,節省存儲空間目的。索引壓縮之後一個索引塊能夠存放更多的鍵值,這樣當進行full index scan,full fast index scan 的時候IO性能會更好,可是CPU的負載會增長,至於整體的性能就要看IO性能的提升和CPU負載增長那個是主要方面了。我不認爲索引壓縮性能老是提升的,更多的意義在於節省存儲空間,減小IO時間。
SQL> create table objects1 as select object_id, object_name from dba_objects; SQL> create table objects2 as select 100 object_id, object_name from dba_objects; SQL> create table objects3 as select object_id, object_name from dba_objects; SQL> create index objects1_idx on objects1(object_id) compress 1; Index created. SQL> create index objects2_inx on objects2(object_id) compress 1; Index created. SQL> create index objects3_inx on objects3(object_id); Index created. SQL> select index_name, compression, leaf_blocks from user_indexes where index_name in ('OBJECTS1_IDX','OBJECTS2_INX','OBJECTS3_INX'); INDEX_NAME COMPRESS LEAF_BLOCKS ------------------------------ -------- ----------- OBJECTS1_IDX ENABLED 230 OBJECTS2_INX ENABLED 116 OBJECTS3_INX DISABLED 167 SQL> select object_id,object_name from objects1 where object_id = 100; -------------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | -------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 29 | 2 (0)| 00:00:01 | | 1 | TABLE ACCESS BY INDEX ROWID| OBJECTS1 | 1 | 29 | 2 (0)| 00:00:01 | |* 2 | INDEX RANGE SCAN | OBJECTS1_IDX | 1 | | 1 (0)| 00:00:01 | -------------------------------------------------------------------------------------------- SQL> select object_id,object_name from objects2 where object_id = 100; ------------------------------------------------------------------------------ | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ------------------------------------------------------------------------------ | 0 | SELECT STATEMENT | | 75203 | 1982K| 98 (2)| 00:00:02 | |* 1 | TABLE ACCESS FULL| OBJECTS2 | 75203 | 1982K| 98 (2)| 00:00:02 | ------------------------------------------------------------------------------ SQL> select object_id,object_name from objects3 where object_id = 100; -------------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | -------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 29 | 2 (0)| 00:00:01 | | 1 | TABLE ACCESS BY INDEX ROWID| OBJECTS3 | 1 | 29 | 2 (0)| 00:00:01 | |* 2 | INDEX RANGE SCAN | OBJECTS3_INX | 1 | | 1 (0)| 00:00:01 | --------------------------------------------------------------------------------------------
咱們能夠看到對於objects1 和 objects3 由於object_id 都是惟一的,因此沒有壓縮的空間,壓縮之後索引反而佔用了更大的空間,還不如不壓縮。而objects2 中 object_id 都是重複的壓縮效果明顯。
除了建立的時候進行索引壓縮,還能夠在rebuild index 的時候指定索引壓縮和解壓縮。
SQL> alter index objects1_idx rebuild nocompress; Index altered. SQL> alter index objects1_idx rebuild compress; Index altered.
注:壓縮也是會引入存儲開銷的,只是不少時候壓縮節省的空間比壓縮須要的存儲開銷更大,因此壓縮之後總體的存儲開銷減少了。 compress 後面接的數字表示的是prefix 的深度,也就是須要用來壓縮的columns 的數量。
####4.4.6 順序索引
The DESC keyword on the CREATE INDEX statement is no longer ignored. It specifies that the index should be created in descending order. Indexes on character data are created in descending order of the character values in the database character set. Neither this, nor the ASC keyword, may be specified for a domain index. DESC cannot be specified for a bitmapped index.:
# would benefit from an index like this: CREATE INDEX c_id_desc ON Citites(city_id DESC) SELECT * FROM Cities ORDER BY city_id DESC # would benefit from an index like this: CREATE INDEX f_miles_desc on Flights(miles DESC) SELECT MAX(miles) FROM Flight # would benefit from an index like this: CREATE INDEX arrival_time_desc ON Flights(dest_airport, arrive_time DESC) SELECT * FROM Flights WHERE dest_airport = 'LAX' ORDER BY ARRIVAL DESC
SQL> create table t_objects as 2 select object_name, object_id, created, owner 3 from all_objects; SQL> select count(*) from t_objects; COUNT(*) ---------- 74101 #建立升序索引 SQL> create index t_idx_1 on t_objects (object_name, owner); ---the usual index. SQL> select index_name, index_type from user_indexes where table_name = 'T_OBJECTS'; INDEX_NAME INDEX_TYPE -------------------- --------------- T_IDX_1 NORMAL SQL> select index_name, table_name, column_name, descend from user_ind_columns where index_name = 'T_IDX_1'; INDEX_NAME TABLE_NAME COLUMN_NAME DESC -------------------- -------------------- -------------------- ---- T_IDX_1 T_OBJECTS OBJECT_NAME ASC T_IDX_1 T_OBJECTS OWNER ASC #the database does not use descending indexes until you first analyze the index and the table on which the index is defined SQL> select * from t_objects 2 where object_name between 'Y' and 'Z' 3 order by object_name asc, owner asc; -------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | -------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1004 | 43172 | 141 (2)| 00:00:02 | | 1 | SORT ORDER BY | | 1004 | 43172 | 141 (2)| 00:00:02 | |* 2 | TABLE ACCESS FULL| T_OBJECTS | 1004 | 43172 | 140 (1)| 00:00:02 | -------------------------------------------------------------------------------- SQL> analyze table t_objects 2 compute statistics 3 for all columns 4 for all indexes; Table analyzed. SQL> select * from t_objects where object_name between 'Y' and 'Z' order by object_name asc, owner asc; ----------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ----------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 82 | 3280 | 43 (0)| 00:00:01 | | 1 | TABLE ACCESS BY INDEX ROWID| T_OBJECTS | 82 | 3280 | 43 (0)| 00:00:01 | |* 2 | INDEX RANGE SCAN | T_IDX_1 | 82 | | 3 (0)| 00:00:01 | ----------------------------------------------------------------------------------------- SQL> select * from t_objects where object_name between 'Y' and 'Z' order by object_name desc, owner desc; ------------------------------------------------------------------------------------------ | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ------------------------------------------------------------------------------------------ | 0 | SELECT STATEMENT | | 82 | 3280 | 43 (0)| 00:00:01 | | 1 | TABLE ACCESS BY INDEX ROWID | T_OBJECTS | 82 | 3280 | 43 (0)| 00:00:01 | |* 2 | INDEX RANGE SCAN DESCENDING| T_IDX_1 | 82 | | 3 (0)| 00:00:01 | ------------------------------------------------------------------------------------------ #建立降序索引 SQL> create index t_inx_1 on t_objects(object_name desc, owner desc); Index created. SQL> select index_name, table_name, column_name, descend from user_ind_columns where index_name = 'T_INX_1'; INDEX_NAME TABLE_NAME COLUMN_NAME DESC -------------------- -------------------- -------------------- ---- T_INX_1 T_OBJECTS SYS_NC00005$ DESC T_INX_1 T_OBJECTS SYS_NC00006$ DESC SQL> select * from t_objects where object_name between 'Y' and 'Z' order by object_name asc, owner asc; ------------------------------------------------------------------------------------------ | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ------------------------------------------------------------------------------------------ | 0 | SELECT STATEMENT | | 82 | 3280 | 29 (0)| 00:00:01 | | 1 | TABLE ACCESS BY INDEX ROWID | T_OBJECTS | 82 | 3280 | 29 (0)| 00:00:01 | |* 2 | INDEX RANGE SCAN DESCENDING| T_INX_1 | 47 | | 5 (0)| 00:00:01 | ------------------------------------------------------------------------------------------ SQL> select * from t_objects where object_name between 'Y' and 'Z' order by object_name desc, owner desc; ----------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ----------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 82 | 3280 | 29 (0)| 00:00:01 | | 1 | TABLE ACCESS BY INDEX ROWID| T_OBJECTS | 82 | 3280 | 29 (0)| 00:00:01 | |* 2 | INDEX RANGE SCAN | T_INX_1 | 47 | | 5 (0)| 00:00:01 | -----------------------------------------------------------------------------------------