我们先创建测试表 T。
create table t as select * from dba_objects;
Table created.
现在有如下语句。
select * from t where object_name like '%SEQ%';
因为需要对字符串两边进行模糊匹配,而索引根块和分支块存储的是前缀数据(也就是说object like 'SEQ%'
才能走索引),所以上面 SQL 查询无法走索引。
如果强制走索引,会走 INDEX FULL SCAN。
create index idx_ojbname on t(object_name);
Index created.
查看强制走索引的执行计划。
select /*+ index(t) */ * from t where object_name like '%SEQ%';
208 rows selected.
Execution Plan
----------------------------------------------------------
Plan hash value: 3894507753
-------------------------------------------------------------------------------------
| Id | Operation |Name | Rows | Bytes | Cost(%CPU)|Time |
-------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 219 | 45333 | 2214 (1)|00:00:27|
| 1 | TABLE ACCESS BY INDEX ROWID|T | 219 | 45333 | 2214 (1)|00:00:27|
|* 2 | INDEX FULL SCAN |IDX_OJBNAME| 3395 | | 362 (1)|00:00:05|
-------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - filter("OBJECT_NAME" LIKE '%SEQ%')
INDEX FULL SCAN 是单块读,性能不如全表扫描。大家可能会有疑问,可不可以走 INDEX FAST FULL SCAN 呢?答案是不可以,因为 INDEX FAST FULL SCAN 不能回表,而上面 SQL 查询需要回表(select *)。
我们可以创建一个表当索引用,用来代替 INDEX FAST FULL SCAN 不能回表的情况。
create table index_t as select object_name,rowid rid from t;
Table created.
现在将 SQL 查询改写为如下 SQL。
select *
from t
where rowid in (select rid from index_t where object_name like '%SEQ%');
改写完 SQL 之后,需要让 index_t 与 t 走嵌套循环,同时让 index_t 作为嵌套循环驱动表,这样就达到了让 index_t 充当索引的目的。
现在我们来对比两个 SQL 的 autotrace 执行计划。
select * from t where object_name like '%SEQ%';
208 rows selected.
Execution Plan
----------------------------------------------------------
Plan hash value: 1601196873
--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 135 | 27945 | 235 (1)| 00:00:03 |
|* 1 | TABLE ACCESS FULL| T | 135 | 27945 | 235 (1)| 00:00:03 |
--------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter("OBJECT_NAME" IS NOT NULL AND "OBJECT_NAME" LIKE '%SEQ%')
Note
-----
- dynamic sampling used for this statement (level=2)
Statistics
----------------------------------------------------------
5 recursive calls
0 db block gets
1117 consistent gets
0 physical reads
0 redo size
12820 bytes sent via SQL*Net to client
563 bytes received via SQL*Net from client
15 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
208 rows processed
select /*+ leading(index_t@a) use_nl(index_t@a,t) */
*
from t
where rowid in (select /*+ qb_name(a) */
rid
from index_t
where object_name like '%SEQ%');
208 rows selected.
Execution Plan
----------------------------------------------------------
Plan hash value: 2608052908
-------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 87 | 25839 | 140 (2)| 00:00:02 |
| 1 | NESTED LOOPS | | 87 | 25839 | 140 (2)| 00:00:02 |
| 2 | SORT UNIQUE | | 87 | 6786 | 95 (2)| 00:00:02 |
|* 3 | TABLE ACCESS FULL | INDEX_T | 87 | 6786 | 95 (2)| 00:00:02 |
| 4 | TABLE ACCESS BY USER ROWID| T | 1 | 219 | 1 (0)| 00:00:01 |
-------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
3 - filter("OBJECT_NAME" IS NOT NULL AND "OBJECT_NAME" LIKE '%SEQ%')
Note
-----
- dynamic sampling used for this statement (level=2)
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
499 consistent gets
0 physical reads
0 redo size
12820 bytes sent via SQL*Net to client
563 bytes received via SQL*Net from client
15 SQL*Net roundtrips to/from client
1 sorts (memory)
0 sorts (disk)
208 rows processed
因为 t 表很小,表字段也不多,所以大家可能感觉性能提升不是特别大。当 t 表越大,性能提升就越明显。采用这个方法还需要对 index_t 进行数据同步,我们可以将 index_t 创建为物化视图,刷新方式采用 on commit 刷新。