在Oracle世界裏,索引主要有B樹索引,位圖索引和全文索引等,其中最常見於OLTP系統的是B樹索引,位圖索引和全文索引則多用於OLAP系統中;索引就類似於一本書的目錄,若只想瞭解某個章節,則在不需要通讀全書,可以通過索引迅速的找到需要的內容;若需要通讀全書,恐怕沒有人會看一頁文章內容,再回頭看下目錄這樣閱讀吧,這2次I/O,必然浪費時間和資源;B樹索引比較適合值分佈比較均勻的場合,因而普遍用於OLTP系統中;位圖索引則適用於取值範圍相對較小,且更改頻率低的場合,相比B樹索引,位圖索引將佔用更少的存儲空間;全文索引則類似分詞系統,一般用在CLOB字段或者TEXT字段上,全文索引會佔用很大的儲存空間,有可能索引的大小要遠大於基表本身;索引會隨着基表的更改而被oracle自動維護!下面就來簡要的談談B樹索引!
一:下列幾種情況將不使用索引
1:使用不等於操作符
SQL> create table t1 as select * from dba_source; SQL> create index ind_t1 on t1(name); SQL> set autot trace exp stat Execution Plan -------------------------------------------------------------------------- Predicate Information (identified by operation id): 1 - filter("NAME"<>'STANDARD') Note Statistics |
2: 使用IS NULL或者IS NOT NULL
SQL> exec dbms_stats.gather_table_stats('HR','T1',cascade=>true); SQL> select * from t1 where name is null; Execution Plan -------------------------------------------------------------------------- Predicate Information (identified by operation id): 1 - filter("NAME" IS NULL) Statistics |
3:不使用基於函數的索引條件下,使用函數
SQL> select * from t1 where lower(name)='standard'; Execution Plan -------------------------------------------------------------------------- Predicate Information (identified by operation id): 1 - filter(LOWER("NAME")='standard') Statistics |
4:比較不匹配的數據類型
SQL> create table t2 (id varchar2(10),name varchar2(10)); SQL> begin SQL> create index ind_t2 on t2(id); SQL> exec dbms_stats.gather_table_stats('HR','T2',cascade=>true); SQL> select * from t2 where id=100; Execution Plan -------------------------------------------------------------------------- Predicate Information (identified by operation id): 1 - filter(TO_NUMBER("ID")=100) Statistics |
二:使用B樹索引
1:新建2張表,並分別對name字段創建索引,收集表統計信息;t2表的name字段爲傾斜型,t1表則相對比較均勻;
- SQL> grant select_catalog_role to sale;
- Grant succeeded.
- SQL> conn sale/123456
- Connected.
- SQL> create table t1 as select * from dba_source;
- Table created.
- SQL> create table t2 as select * from dba_source;
- Table created.
- SQL> update t2 set name='SALE';
- 292436 rows updated.
- SQL> commit;
- Commit complete.
- SQL> update t2 set name='T2' where rownum < 2;
- 1 row updated.
- SQL> commit;
- Commit complete.
- SQL> create index ind_t1 on t1(name);
- Index created.
- SQL> create index ind_t2 on t2(name);
- Index created.
- SQL> exec dbms_stats.gather_table_stats('sale','t1',cascade=>true);
- PL/SQL procedure successfully completed.
- SQL> exec dbms_stats.gather_table_stats('sale','t2',cascade=>true);
- PL/SQL procedure successfully completed.
2:對t1表進行查詢,可以看到,查詢1使用索引ind_t1,查詢2根據hint提示使用全表掃描,對比兩次查詢的CPU代價和一致性讀,可以看出索引的優勢!
SQL> set autot trace exp stat Execution Plan -------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time -------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 476 | 55692 | 18 (0)| 00:0 | 1 | TABLE ACCESS BY INDEX ROWID| T1 | 476 | 55692 | 18 (0)| 00:0 |* 2 | INDEX RANGE SCAN | IND_T1 | 476 | | 4 (0)| 00:0 -------------------------------------------------------------------------------- Predicate Information (identified by operation id): 2 - access("NAME"='STANDARD') Statistics SQL> select /*+full(t1)*/ * from t1 where name='STANDARD'; Execution Plan -------------------------------------------------------------------------- Predicate Information (identified by operation id): 1 - filter("NAME"='STANDARD') Statistics |
3:對t2表進行查詢,可以看到,查詢1使用索引ind_t2,查詢2則使用全表掃描,因爲name字段基於所以的值均爲SALE,所以這個時候使用全表掃描反而更快,查詢3使用hint提示使用ind_t2索引,將產生更大的開銷,由此可見,B樹索引適合於值分佈比較均勻的場合,否則2次I/O將導致查詢更慢!
SQL> set autot trace exp stat Execution Plan -------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time -------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 107 | 4 (0)| 00:0 | 1 | TABLE ACCESS BY INDEX ROWID| T2 | 1 | 107 | 4 (0)| 00:0 |* 2 | INDEX RANGE SCAN | IND_T2 | 1 | | 3 (0)| 00:0 -------------------------------------------------------------------------------- Predicate Information (identified by operation id): 2 - access("NAME"='T2') Statistics SQL> select * from t2 where name='SALE'; Execution Plan -------------------------------------------------------------------------- Predicate Information (identified by operation id): 1 - filter("NAME"='SALE') Statistics SQL> select /*+index(t2 ind_t2)*/ * from t2 where name='SALE'; Execution Plan -------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time -------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 291K| 29M| 6850 (1)| 00:0 | 1 | TABLE ACCESS BY INDEX ROWID| T2 | 291K| 29M| 6850 (1)| 00:0 |* 2 | INDEX RANGE SCAN | IND_T2 | 292K| | 662 (2)| 00:0 -------------------------------------------------------------------------------- Predicate Information (identified by operation id): 2 - access("NAME"='SALE') Statistics |
三:表和索引的壓縮,在大數據量條件下,除了使用分區技術來裁剪I/O外,壓縮也是個不錯的選擇,但壓縮比較適合於更新不頻繁的場景
1:查看T1表和IND_T1索引的DDL創建語句和存儲佔用情況
- SQL> show user;
- USER is "SYS"
- SQL> set long 5000
- SQL> set heading off
- SQL> select dbms_metadata.get_ddl('TABLE', 'T1', 'SALE') from dual;
- CREATE TABLE "SALE"."T1"
- ( "OWNER" VARCHAR2(30),
- "NAME" VARCHAR2(30),
- "TYPE" VARCHAR2(12),
- "LINE" NUMBER,
- "TEXT" VARCHAR2(4000)
- ) PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS 255 NOCOMP
- RESS LOGGING
- STORAGE(INITIAL 65536 NEXT 1048576 MINEXTENTS
- 1 MAXEXTENTS 2147483645
- PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1 BUFFER_PO
- OL DEFAULT)
- TABLESPACE "USERS"
- SQL> select dbms_metadata.get_ddl('INDEX', 'IND_T1', 'SALE') from dual;
- CREATE INDEX "SALE"."IND_T1" ON "SALE"."T1" ("NAME")
- PCTFREE 10 INITRANS 2 MAXTRANS 255 COMPUT
- E STATISTICS
- STORAGE(INITIAL 65536 NEXT 1048576 MINEXTENT
- S 1 MAXEXTENTS 2147483645
- PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1 BUFFER_P
- OOL DEFAULT)
- TABLESPACE "USERS"
- SQL> desc show_space;
- PROCEDURE show_space
- Argument Name Type In/Out Default?
- ------------------------------ ----------------------- ------ --------
- P_SEGNAME VARCHAR2 IN
- P_OWNER VARCHAR2 IN DEFAULT
- P_TYPE VARCHAR2 IN DEFAULT
- P_PARTITION VARCHAR2 IN DEFAULT
- SQL> set serveroutput on
- SQL> exec show_space('T1','SALE','table');
- Free Blocks.............................
- Total Blocks............................6400
- Total Bytes.............................52428800
- Total MBytes............................50
- Unused Blocks...........................118
- Unused Bytes............................966656
- Last Used Ext FileId....................4
- Last Used Ext BlockId...................6665
- Last Used Block.........................10
- PL/SQL procedure successfully completed.
- SQL> exec show_space('IND_T1','SALE','index');
- Free Blocks.............................
- Total Blocks............................1152
- Total Bytes.............................9437184
- Total MBytes............................9
- Unused Blocks...........................114
- Unused Bytes............................933888
- Last Used Ext FileId....................4
- Last Used Ext BlockId...................14217
- Last Used Block.........................14
- PL/SQL procedure successfully completed.
2:新建表t3和索引ind_t3,帶壓縮屬性,對比表t1所佔的存儲,表爲50M:37M,索引爲9M:4M
- SQL> create table t3 compress as select * from t1;
- Table created.
- SQL> select table_name,compression from user_tables where table_name like 'T%';
- TABLE_NAME COMPRESS
- ------------------------------ --------
- T3 ENABLED
- T2 DISABLED
- T1 DISABLED
- SQL> exec show_space('T3','SALE','table');
- Free Blocks.............................
- Total Blocks............................4736
- Total Bytes.............................38797312
- Total MBytes............................37
- Unused Blocks...........................70
- Unused Bytes............................573440
- Last Used Ext FileId....................4
- Last Used Ext BlockId...................19849
- Last Used Block.........................58
- PL/SQL procedure successfully completed.
- SQL> create index ind_t3 on t3(name) compress;
- Index created.
- SQL> select table_name,compression from user_indexes where table_name like 'T%';
- TABLE_NAME COMPRESS
- ------------------------------ --------
- T3 ENABLED
- T2 DISABLED
- T1 DISABLED
- SQL> exec show_space('IND_T3','SALE','index');
- Free Blocks.............................
- Total Blocks............................512
- Total Bytes.............................4194304
- Total MBytes............................4
- Unused Blocks...........................32
- Unused Bytes............................262144
- Last Used Ext FileId....................4
- Last Used Ext BlockId...................20361
- Last Used Block.........................96
- PL/SQL procedure successfully completed.
3:分區表的本地索引壓縮,需要先在對象級別上啓用壓縮
- SQL> select table_name from user_part_tables;
- TABLE_NAME
- ------------------------------
- SALE_DATA
- EMP
- SQL> select index_name from user_part_indexes where table_name in (select table_name from user_part_tables);
- INDEX_NAME
- ------------------------------
- IND_SALE_DATA_DATE
- SQL> select partition_name,tablespace_name,compression from user_ind_partitions where
- index_name='IND_SALE_DATA_DATE';
- PARTITION_NAME TABLESPACE_NAME COMPRESS
- ------------------------------ ------------------------------ --------
- SALES_01 TBS_SALE01 DISABLED
- SALES_02 TBS_SALE02 DISABLED
- SALES_03 TBS_SALE03 DISABLED
- SALES_04 TBS_SALE04 DISABLED
- SALES_05 TBS_SALE05 DISABLED
- SALES_06 TBS_SALE06 DISABLED
- SALES_07 TBS_SALE07 DISABLED
- SALES_08 TBS_SALE08 DISABLED
- SALES_09 TBS_SALE09 DISABLED
- SALES_10 TBS_SALE10 DISABLED
- SALES_11 TBS_SALE11 DISABLED
- PARTITION_NAME TABLESPACE_NAME COMPRESS
- ------------------------------ ------------------------------ --------
- SALES_12 TBS_SALE12 DISABLED
- SQL> alter index IND_SALE_DATA_DATE modify partition sales_01 compress;
- alter index IND_SALE_DATA_DATE modify partition sales_01 compress
- *
- ERROR at line 1:
- ORA-28659: COMPRESS must be specified at object level first
- SQL> drop index IND_SALE_DATA_DATE;
- Index dropped.
- SQL> create index ind_sale_data_date on sale_data(sale_id) local compress;
- Index created.
- SQL> select partition_name,tablespace_name,compression from user_ind_partitions where
- index_name='IND_SALE_DATA_DATE';
- PARTITION_NAME TABLESPACE_NAME COMPRESS
- ------------------------------ ------------------------------ --------
- SALES_01 TBS_SALE01 ENABLED
- SALES_02 TBS_SALE02 ENABLED
- SALES_03 TBS_SALE03 ENABLED
- SALES_04 TBS_SALE04 ENABLED
- SALES_05 TBS_SALE05 ENABLED
- SALES_06 TBS_SALE06 ENABLED
- SALES_07 TBS_SALE07 ENABLED
- SALES_08 TBS_SALE08 ENABLED
- SALES_09 TBS_SALE09 ENABLED
- SALES_10 TBS_SALE10 ENABLED
- SALES_11 TBS_SALE11 ENABLED
- PARTITION_NAME TABLESPACE_NAME COMPRESS
- ------------------------------ ------------------------------ --------
- SALES_12 TBS_SALE12 ENABLED
- SQL> alter index ind_sale_data_date modify partition sales_01 nocompress;
- Index altered.
- SQL> alter index ind_sale_data_date rebuild partition sales_02 nocompress;
- Index altered.
- SQL> select partition_name,tablespace_name,compression from user_ind_partitions where
- index_name='IND_SALE_DATA_DATE';
- PARTITION_NAME TABLESPACE_NAME COMPRESS
- ------------------------------ ------------------------------ --------
- SALES_01 TBS_SALE01 DISABLED
- SALES_02 TBS_SALE02 DISABLED
- SALES_03 TBS_SALE03 ENABLED
- SALES_04 TBS_SALE04 ENABLED
- SALES_05 TBS_SALE05 ENABLED
- SALES_06 TBS_SALE06 ENABLED
- SALES_07 TBS_SALE07 ENABLED
- SALES_08 TBS_SALE08 ENABLED
- SALES_09 TBS_SALE09 ENABLED
- SALES_10 TBS_SALE10 ENABLED
- SALES_11 TBS_SALE11 ENABLED
- PARTITION_NAME TABLESPACE_NAME COMPRESS
- ------------------------------ ------------------------------ --------
- SALES_12 TBS_SALE12 ENABLED
四:對象分析與執行計劃,oracle 10g以後關於sql語句的執行,默認都是使用cbo,即基於代價的優化器,而不是基於規則的,這個代價則是有對象分析而來,也就是我們平常說的統計信息,下面來看下當統計信息未及時更新的情況下,sql執行計劃的選擇!
1:創建基表和索引,可以看到,當沒有統計信息的時候,oracle將採取動態採樣方式,也可以獲得正確的執行計劃
SQL> create table t4 as select 100 object_id,object_name from dba_objects; SQL> update t4 set object_id=1 where rownum=1; SQL> commit; SQL> create index ind_t4 on t4(object_id); SQL> set autot trace exp stat Execution Plan -------------------------------------------------------------------------- Predicate Information (identified by operation id): 1 - filter("OBJECT_ID"=100) Note Statistics SQL> select * from t4 where object_id=1; Execution Plan -------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time -------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 79 | 2 (0)| 00:0 | 1 | TABLE ACCESS BY INDEX ROWID| T4 | 1 | 79 | 2 (0)| 00:0 |* 2 | INDEX RANGE SCAN | IND_T4 | 1 | | 1 (0)| 00:0 -------------------------------------------------------------------------------- Predicate Information (identified by operation id): 2 - access("OBJECT_ID"=1) Note Statistics |
2:刪除表中所有的數據後,收集統計信息,然後再插入原先一樣的數據
- SQL> delete from t4;
- 50415 rows deleted.
- SQL> exec dbms_stats.gather_table_stats('sale','t4',cascade=>true);
- PL/SQL procedure successfully completed.
- SQL> insert into t4 select 100 object_id,object_name from dba_objects;
- 50416 rows created.
- SQL> update t4 set object_id=1 where rownum=1;
- 1 row updated.
- SQL> commit
- Commit complete.
- SQL> select object_id,count(*) from t4 group by object_id;
- OBJECT_ID COUNT(*)
- ---------- ----------
- 1 1
- 100 50415
3:這個時候,統計信息未及時更新,認爲下面的查詢語句選擇走索引會更優,則會產生錯誤的執行計劃,導致查詢語句緩慢!在生產環境,大數據量情況下尤爲明顯!
SQL> set autot trace exp stat Execution Plan -------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time -------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 79 | 1 (0)| 00:0 | 1 | TABLE ACCESS BY INDEX ROWID| T4 | 1 | 79 | 1 (0)| 00:0 |* 2 | INDEX RANGE SCAN | IND_T4 | 1 | | 1 (0)| 00:0 -------------------------------------------------------------------------------- Predicate Information (identified by operation id): 2 - access("OBJECT_ID"=100) Statistics |