基于函数的索引

利用基于函数的索引,我们能够对计算得出的列建立索引,并在查询中使用这些索引。
使用基于函数的索引可能有很多原因,其中主要的原因如下:
使用索引很容易实现,并能立即提交一个值。
可以加快现有应用的速度,而不用修改任何逻辑或查询。

使用基于代价的优化器(cost-based optimizer,CBO)。在基于函数的索引中,虚拟列(应用了函数的列)只对CBO可见,而基于规则的优化器(rule-based optimizer, RBO)不能使用这些虚拟列。RBO可以利用基于函数的索引中未应用函数的前几列。
对于返回VARCHAR2或RAW 类型的用户编写的函数,使用SUBSTR来约束其返回值,也可以把SUBSTR隐藏在一个视图中(这是推荐的做法)。

示例:

system@ORCL>create table emp
  2  as
  3  select *
  4  from scott.emp;

表已创建。
system@ORCL>insert into emp
  2  (empno,ename,job,mgr,hiredate,sal,comm,deptno)
  3  select rownum empno,
  4  initcap(substr(object_name,0,5)) ename,
  5  substr(object_type,0,5) JOB,
  6  rownum MGR,
  7  created hiredate,
  8  rownum SAL,
  9  rownum COMM,
 10  (mod(rownum,4)+1)*5  DEPTNO
 11  from all_objects ;

已创建72020行。

system@ORCL>create index emp_upper_idx on emp(upper(ename));

索引已创建。

需要利用CBO来使用基于函数的索引

system@ORCL>begin
  2     dbms_stats.gather_table_stats
  3     (user,'EMP',cascade=>true);
  4  end;
  5  /

PL/SQL 过程已成功完成。

现在就在一个列的UPPER值上建立了一个索引。执行“大小写无关“查询(如以下查询)的任何应用都能利用这个索引:

system@ORCL>set autotrace traceonly explain
system@ORCL>select *
  2  from emp
  3  where upper(ename) = 'KING';

执行计划
----------------------------------------------------------
Plan hash value: 1576737566

--------------------------------------------------------------------------------
-------------
| Id  | Operation                   | Name          | Rows  | Bytes | Cost (%CPU
)| Time     |
--------------------------------------------------------------------------------
-------------
|   0 | SELECT STATEMENT            |               |     4 |   196 |     3   (0
)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| EMP           |     4 |   196 |     3   (0
)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | EMP_UPPER_IDX |     4 |       |     1   (0
)| 00:00:01 |
--------------------------------------------------------------------------------
-------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access(UPPER("ENAME")='KING')

这样就能得到索引所能提供的性能提升。在有这个特性之前,EMP表中的每一行都要扫描、改为大写并进行比较。与之不同,利用UPPER(ENAME)上的索引,查询为索引提供了常量KING,然后只对少量数据执行区间扫描,并按rowid访问表来得到数据。这是相当快的。

对列上的用户编写的函数建立索引时,能更清楚地看到这种性能提升。

select my_function(ename)
from emp
where some_other_function(empno) > 11
/

这很棒,因为现在我们能很好地扩展SQL语言,可以包括应用特定的函数。不过,有时前面这个查询的性能并不让人满意。假设EMP表中有11,000行。查询期间函数SOME_OTHER_FUNCTION就会执行11,000次,每行执行一次。另外,假设这个函数执行时需要百分之一秒的时间,尽管这个查询相对简单,现在也至少需要11秒的时间才能完成。

system@ORCL>create or replace package stats
  2  as
  3     cnt number default 0;
  4  end;
  5  /

程序包已创建。

system@ORCL>create or replace
  2  function my_soundex( p_string in varchar2 ) return varchar2
  3  deterministic
  4  as
  5     l_return_string varchar2(6) default substr( p_string, 1, 1 );
  6     l_char varchar2(1);
  7     l_last_digit number default 0;
  8     type vcArray is table of varchar2(11) index by binary_integer;
  9     l_code_table vcArray;
 10
 11  begin
 12     stats.cnt := stats.cnt+1;
 13
 14     l_code_table(1) := 'BPFV';
 15     l_code_table(2) := 'CSKGJQXZ';
 16     l_code_table(3) := 'DT';
 17     l_code_table(4) := 'L';
 18     l_code_table(5) := 'MN';
 19     l_code_table(6) := 'R';
 20  for i in 1 .. length(p_string)
 21  loop
 22     exit when (length(l_return_string) = 6);
 23     l_char := upper(substr( p_string, i, 1 ) );
 24
 25     for j in 1 .. l_code_table.count
 26             loop
 27                     if (instr(l_code_table(j), l_char ) > 0 AND j <> l_last_
digit)
 28                     then
 29                             l_return_string := l_return_string || to_char(j,
'fm9');
 30                             l_last_digit := j;
 31                     end if;
 32             end loop;
 33  end loop;
 34
 35  return rpad( l_return_string, 6, '0' );
 36  end;
 37  /

函数已创建。

注意在这个函数中,我们使用了一个关键字DETERMINISTIC。这就声明了:前面这个函数在给定相同的输入时,总会返回完全相同的输出。要在一个用户编写的函数上创建索引,这个关键字是必要的。我们必须告诉Oracle这个函数是确定性的(DETERMINISTIC),而且在给定相同输入的情况下总会返回一致的结果。如果不是这样,通过索引访问数据时就会得到与全表扫描不同的答案。这种确定性设置表明在有些函数上是不能建立索引的,例如,我们无法在函数DBMS_RANDOM.RANDOM上创建索引,因为这是一个随机数生成器。函数DBMS_RANDOM.RANDOM的结果不是确定性的;给定相同的输入,我们会得到随机的输出。另一方面,第一个例子中所用的内置SQL函数UPPER则是确定性的,所有可以在列的UPPER值上创建一个索引。

既然有了函数MY_SOUNDEX,下面来看没有索引时表现如何。在此使用了前面创建的EMP表(其中有大约11,000行):

system@ORCL>set timing on
system@ORCL>set autotrace on explain
system@ORCL>select ename, hiredate
  2  from emp
  3  where my_soundex(ename) = my_soundex('Kings')
  4  /

未选定行

已用时间:  00: 00: 01.21

执行计划
----------------------------------------------------------
Plan hash value: 3956160932

--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      |   720 | 10080 |   162  (20)| 00:00:02 |
|*  1 |  TABLE ACCESS FULL| EMP  |   720 | 10080 |   162  (20)| 00:00:02 |
--------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter("MY_SOUNDEX"("ENAME")="MY_SOUNDEX"('Kings'))

system@ORCL>set autotrace off
system@ORCL>set timing off
system@ORCL>set serveroutput on
system@ORCL>exec dbms_output.put_line( stats.cnt );
72002

PL/SQL 过程已成功完成。

可以看到这个查询花了一秒多的时间执行,而且必须执行全表扫描。函数MY_SOUNDEX被调用了 72,002 次(根据计数器得出),每行要调用两次。

system@ORCL>create index emp_soundex_idx on
  2  emp( substr(my_soundex(ename),11.6) )
  3  /

索引已创建。

在这个CREATE INDEX命令中,有意思的是在此使用了SUBSTR函数。这是因为,我们在对一个返回串的函数建索引。如果对一个返回数字或日期的函数建索引,就没有必须使用这个SUBSTR。如果用户编写的函数返回一个串,之所以要对这样一个函数使用SUBSTR,原因是这种函数会返回VARCHAR2(4000)类型。这就太大了,无法建立索引,索引条目必须能在块大小的3/4中放得下。

这并不是说索引中确实包含那么大的键,而是说对数据库而言键可以有这么大。但是数据库“看得懂“SUBSTR。它看到SUBSTR的输入参数为1和6,知道最大的返回值是6个字符;因此,它允许创建索引。你很可能会遇到这种大小问题,特别是对于串联索引。以下是一个例子,在此表空间的块大小为8KB:

system@ORCL>create index emp_soundex_idx on
  2  emp( my_soundex(ename), my_soundex(job) );
emp( my_soundex(ename), my_soundex(job) )
                                   *
第 2 行出现错误:
ORA-01450: 超出最大的关键字长度 (6398)

在此,数据库认为最大的键为6,398,所以CREATE失败。因此,如果用户编写的函数要返回一个串,要对这样一个函数建立索引,应当在CREATE INDEX语句中对返回类型有所限制。在这个例子中,由于知道MY_SOUNDEX最多返回6个字符,所以取前6个字符作为字串。

有了这个索引后,现在来测试表的性能。我们想监视索引对INSERT的影响,并观察它能怎样加快SELECT的执行速度。在没有索引的测试用例中,我们的查询用了1秒多的时间,如果在插入期间运行SQL_TRACE和TKPROF,会观察到:在没有索引的情况下,插入9,999条记录耗时约0.5秒。

原因在于管理MY_SOUNDEX函数上的新索引会带来开销,这一方面是因为只要有索引就存在相应的性能开销(任何类型的索引都会影响插入的性能);另一方面是因为这个索引必须把一个存储过程调用9,999次。

下面测试这个查询,只需再次运行查询:

system@ORCL>REM reset our counter
system@ORCL>exec stats.cnt := 0

PL/SQL 过程已成功完成。

system@ORCL>set timing on
system@ORCL>set autotrace on explain
system@ORCL>select ename, hiredate
  2  from emp
  3  where substr(my_soundex(ename),1,6) = my_soundex('Kings')
  4  /

未选定行

已用时间:  00: 00: 00.03

执行计划
----------------------------------------------------------
Plan hash value: 2259214921

--------------------------------------------------------------------------------
---------------
| Id  | Operation                   | Name            | Rows  | Bytes | Cost (%C
PU)| Time     |
--------------------------------------------------------------------------------
---------------
|   0 | SELECT STATEMENT            |                 |   720 | 15840 |    28
(0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| EMP             |   720 | 15840 |    28
(0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | EMP_SOUNDEX_IDX |   288 |       |     1
(0)| 00:00:01 |
--------------------------------------------------------------------------------
---------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access(SUBSTR("SYSTEM"."MY_SOUNDEX"("ENAME"),1,6)="MY_SOUNDEX"('Kings'))

system@ORCL>set autotrace off
system@ORCL>set timing off
system@ORCL>set serveroutput on
system@ORCL>exec dbms_output.put_line( stats.cnt );
1

PL/SQL 过程已成功完成。

如果对这两个例子做个比较(无索引和有索引),会发现插入受到的影响是:其运行时间是原来的两倍还多。不过,选择操作则不同,原来需要1秒多的时间,现在几乎是“立即”完成。这里的要点是:

有索引时,插入9,999条记录需要大约两倍多的时间。对用户编程的函数建立索引绝对会影响插入(和一些更新)的性能。
尽管插入的运行速度慢了两倍多,但查询运行的速度却快了几倍。它只是把MY_SOUNDEX函数计算了几次,而不是几乎72,000次。这里有索引和无索引时查询性能的差异相当显著。另外,表越大,全面扫描查询执行的时间就会越来越长。基于索引的查询则不同,随着表的增大,基于索引的查询总是有几乎相同的执行性能。
在我们的查询中必须使用SUBSTR。

因此,插入会受到影响,但是查询运行得快得多。尽管插入/更新性能稍有下降,但是回报是丰厚的。另外,如果从不更新MY_SOUNDEX函数调用中涉及到的列,更新就根本没有开销(仅当修改了ENAME列而且其值确实有改变时,才会调用MY_SOUNDEX)。

现在来看如何让查询不使用SUBSTR函数调用。使用SUBSTR调用可能很容易出错,最终用户必须知道要从第1个字符起取6个字符作为子串(SUBSTR)。如果使用的子串大小不同,就不会使用这个索引。另外,我们可能希望在服务器中控制要索引的字节数。因此我们可以重新实现MY_SOUNDEX函数,如果愿意还可以索引7个字节而不是6个。利用一个视图就能非常简单地隐藏SUBSTR,如下所示:

system@ORCL>create or replace view emp_v
  2  as
  3  select ename, substr(my_soundex(ename),1,6) ename_soundex, hiredate
  4  from emp
  5  /

视图已创建。

system@ORCL>exec stats.cnt := 0;

PL/SQL 过程已成功完成。

system@ORCL>set timing on
system@ORCL>select ename, hiredate
  2  from emp_v
  3  where ename_soundex = my_soundex('King')
  4  /

未选定行

system@ORCL>set timing off
system@ORCL>exec dbms_output.put_line( stats.cnt )
1

PL/SQL 过程已成功完成。

可以看到这个查询计划与对基表的查询计划是一样的。这里所做的只是将SUBSTR(F(X)),1,6)隐藏在视图本身中。优化器会识别出这个虚拟列实际上是加了索引的列,并采取“正确”的行动。我们能看到同样的性能提升和同样的查询计划。使用这个视图与使用基表是一样的,甚至还更好一些,因为它隐藏了复杂性,并允许我们以后改变SUBSTR的大小。

3只对部分行建立索引

基于函数的索引除了对使用内置函数(如UPPER、LOWER等)的查询显然有帮助之外,还可以用来有选择地只是对表中的某些行建立索引。

B*树索引对于完全为NULL的键没有相应的条目。也就是说,如果在表T上有一个索引I:

Create index I on t(a,b);

而且行中A和B都为NULL,索引结构中就没有相应的条目。如果只对表中的某些行建立索引,这就能用得上。

考虑有一个很大的表,其中有一个NOT NULL列,名为PROCESSED_FLAG,它有两个可取值:Y或N,默认值为N。增加新行时,这个值为N,指示这一行未得到处理,等到处理了这一行后,则会将其更新为Y来指示已处理。我们可能想对这个列建立索引,从而能快速地获取值为N的记录,但是这里有数百万行,而且几乎所有行的值都为Y。所得到的B*树索引将会很大,如果我们把值从N更新为Y,维护这样一个大索引的开销也相当高。这个表听起来很适合采用位图索引(毕竟基数很低!),但这是一个事务性系统,可能有很多人在同时插入记录(新记录的“是否处理”列设置为N),位图索引不适用于并发修改。如果考虑到这个表中会不断地将N更新为Y,那位图就更不合适了,根本不应考虑,因为这个过程会完全串行化。

所以,我们真正想做的是,只对感兴趣的记录建立索引(即该列值为N的记录)。我们会介绍如何利用基于函数的索引来做到这一点,但是在此之前,先来看如果只是一个常规索引会发生什么。使用本书最前面“环境设置”一节中描述的标准BIG_TABLE脚本,下面更新TEMPORARY列,在此将Y变成N,以及N变成Y:

scott@ORCL>update big_table set temporary = decode(temporary,'N','Y','N');

已更新100000行。

现在检查Y与N地比例:

scott@ORCL>select temporary, cnt,
  2     round( (ratio_to_report(cnt) over ()) * 100, 2 ) rtr
  3  from (
  4             select temporary, count(*) cnt
  5             from big_table
  6             group by temporary
  7     )
  8  /

T        CNT        RTR
- ---------- ----------
Y      99773      99.77
N        227        .23

可以看到,在表中,只有0.2%的数据应当加索引。如果在TEMPORARY列上使用传统索引(相对于这个例子中PROCESSED_FLAG列的角色),会发现这个索引有100,000个条目,占用了超过1.4MB的空间,其高度为2:

scott@ORCL>create index processed_flag_idx
  2  on big_table(temporary);

索引已创建。

scott@ORCL>analyze index processed_flag_idx
  2  validate structure;

索引已分析

scott@ORCL>select name, btree_space, lf_rows, height
  2  from index_stats;

NAME                           BTREE_SPACE    LF_ROWS     HEIGHT
------------------------------ ----------- ---------- ----------
PROCESSED_FLAG_IDX                 1464032     100000          2

通过这个索引获取任何数据都会带来2个I/O才能达到叶子块。这个索引不仅很“宽”,还很“高”。要得到第一个未处理的记录,必须至少执行3个I/O(其中2个是对索引的I/O,另外一个是对表的I/O)。

怎么改变这种情况呢?我们要让索引更小一些,而且要更易维护(更新期间的运行时开销更少)。采用基于函数的索引,我们可以编写一个函数,如果不想对某个给定行加索引,则这个函数就返回NULL;而对想加索引的行则返回一个非NULL值。例如,由于我们只对列值为N的记录感兴趣,所以只对这些记录加索引:

scott@ORCL>drop index processed_flag_idx;

索引已删除。

scott@ORCL>create index processed_flag_idx
  2  on big_table( case temporary when 'N' then 'N' end );

索引已创建。

scott@ORCL>analyze index processed_flag_idx
  2  validate structure;

索引已分析

scott@ORCL>select name, btree_space, lf_rows, height
  2  from index_stats;

NAME                           BTREE_SPACE    LF_ROWS     HEIGHT
------------------------------ ----------- ---------- ----------
PROCESSED_FLAG_IDX                    8000        227          1

这就有很大不同,这个索引只有大约8KB。高度也有所降低。与前面那个更高的索引相比,使用这个索引能少执行一个I/O。

4 实现有选择的惟一性

要利用基于函数的索引,还有一个有用的技术,这就是使用这种索引来保证某种复杂的约束。例如,假设有一个带版本信息的表,如项目表。项目有两种状态:要么为ACTIVE,要么为INACTIVE。需要保证以下规则:“活动的项目必须有一个惟一名;而不活动的项目无此要求。”也就是说,只有一个活动的“项目X”,但是如果你愿意,可以有多个名为X的不活动项目。

开发人员了解到这个需求时,第一反应往往是:“我们只需运行一个查询来查看是否有活动项目X,如果没有,就可以创建一个活动项目X。”这种简单的实现在多用户环境中是不可行的。如果两个人想同时创建一个新的活动项目X,他们都会成功。我们需要将项目X的创建串行化,但是对此惟一的做法是锁住这个项目表(这样做并发性就不太好了),或者使用一个基于函数的索引,让数据库为我们做这个工作。

由于可以在函数上创建索引,而且B*树索引中对于完全为NULL的行没有相应的条目,另外我们可以创建一个UNIQUE索引,基于这几点,可以很容易做到:

Create unique index active_projects_must_be_unique
On projects ( case when status = 'ACTIVE' then name end );

这就行了。状态(status)列是ACTIVE时,NAME列将建立惟一的索引。如果试图创建同名的活动项目,就会被检测到,而且这根本不会影响对这个表的并发访问。

5 关于CASE的警告

某些Oracle版本中有一个bug,其中基于函数的索引中引用的函数会以某种方式被重写,以至于索引无法被透明地使用。例如,前面的CASE语句

Case when temporary = 'N' then 'N' end

会悄悄地重写为以下更高效的语句:

CASE "TEMPORARY" WHEN 'N' THEN 'N' END
scott@ORCL>create table t ( x int );

表已创建。

scott@ORCL>create index t_idx on
  2  t( case when x = 42 then 1 end );

索引已创建。

scott@ORCL>set autotrace traceonly explain
scott@ORCL>select /*+ index( t t_idx ) */ *
  2  from t
  3  where (case when x = 42 then 1 end ) = 1;

执行计划
----------------------------------------------------------
Plan hash value: 470836197

--------------------------------------------------------------------------------
-----
| Id  | Operation                   | Name  | Rows  | Bytes | Cost (%CPU)| Time
    |
--------------------------------------------------------------------------------
-----
|   0 | SELECT STATEMENT            |       |     1 |    16 |     1   (0)| 00:00
:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T     |     1 |    16 |     1   (0)| 00:00
:01 |
|*  2 |   INDEX RANGE SCAN          | T_IDX |     1 |       |     1   (0)| 00:00
:01 |
--------------------------------------------------------------------------------
-----

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access(CASE "X" WHEN 42 THEN 1 END =1)

Note
-----
   - dynamic sampling used for this statement (level=2)

scott@ORCL>select column_expression
  2  from user_ind_expressions
  3  where index_name = 'T_IDX';

执行计划
----------------------------------------------------------
Plan hash value: 3290151145

--------------------------------------------------------------------------------
---------------------------
| Id  | Operation                          | Name                 | Rows  | Byte
s | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------
---------------------------
|   0 | SELECT STATEMENT                   |                      |     2 |   64
8 |   222   (0)| 00:00:03 |
|   1 |  VIEW                              | USER_IND_EXPRESSIONS |     2 |   64
8 |   222   (0)| 00:00:03 |
|   2 |   UNION-ALL                        |                      |       |
  |            |          |
|   3 |    NESTED LOOPS                    |                      |       |
  |            |          |
|   4 |     NESTED LOOPS                   |                      |     1 |    8
2 |   214   (0)| 00:00:03 |
|   5 |      NESTED LOOPS                  |                      |     1 |    7
0 |   213   (0)| 00:00:03 |
|   6 |       NESTED LOOPS                 |                      |     2 |   11
6 |   212   (0)| 00:00:03 |
|   7 |        NESTED LOOPS                |                      |     2 |    9
4 |   208   (0)| 00:00:03 |
|*  8 |         INDEX FAST FULL SCAN       | I_OBJ2               |     2 |    6
0 |   205   (0)| 00:00:03 |
|*  9 |         TABLE ACCESS BY INDEX ROWID| ICOL$                |     1 |    1
7 |     2   (0)| 00:00:01 |
|* 10 |          INDEX RANGE SCAN          | I_ICOL1              |     2 |
  |     1   (0)| 00:00:01 |
|* 11 |        TABLE ACCESS BY INDEX ROWID | OBJ$                 |     1 |    1
1 |     2   (0)| 00:00:01 |
|* 12 |         INDEX RANGE SCAN           | I_OBJ1               |     1 |
  |     1   (0)| 00:00:01 |
|  13 |       TABLE ACCESS BY INDEX ROWID  | COL$                 |     1 |    1
2 |     1   (0)| 00:00:01 |
|* 14 |        INDEX UNIQUE SCAN           | I_COL3               |     1 |
  |     0   (0)| 00:00:01 |
|* 15 |      INDEX UNIQUE SCAN             | I_IND1               |     1 |
  |     0   (0)| 00:00:01 |
|* 16 |     TABLE ACCESS BY INDEX ROWID    | IND$                 |     1 |    1
2 |     1   (0)| 00:00:01 |
|  17 |    NESTED LOOPS                    |                      |       |
  |            |          |
|  18 |     NESTED LOOPS                   |                      |     1 |    8
5 |     8   (0)| 00:00:01 |
|  19 |      NESTED LOOPS                  |                      |     1 |    7
3 |     6   (0)| 00:00:01 |
|  20 |       NESTED LOOPS                 |                      |     1 |    6
1 |     5   (0)| 00:00:01 |
|  21 |        NESTED LOOPS                |                      |     1 |    5
3 |     4   (0)| 00:00:01 |
|* 22 |         INDEX RANGE SCAN           | I_OBJ2               |     1 |    3
6 |     3   (0)| 00:00:01 |
|* 23 |         TABLE ACCESS BY INDEX ROWID| IND$                 |     1 |    1
7 |     1   (0)| 00:00:01 |
|* 24 |          INDEX UNIQUE SCAN         | I_IND1               |     1 |
  |     0   (0)| 00:00:01 |
|* 25 |        INDEX RANGE SCAN            | I_OBJ1               |     1 |
8 |     1   (0)| 00:00:01 |
|  26 |       TABLE ACCESS CLUSTER         | COL$                 |     1 |    1
2 |     1   (0)| 00:00:01 |
|* 27 |        INDEX UNIQUE SCAN           | I_OBJ#               |     1 |
  |     0   (0)| 00:00:01 |
|* 28 |      INDEX RANGE SCAN              | I_ICOL1              |     2 |
  |     1   (0)| 00:00:01 |
|* 29 |     TABLE ACCESS BY INDEX ROWID    | ICOL$                |     1 |    1
2 |     2   (0)| 00:00:01 |
--------------------------------------------------------------------------------
---------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   8 - filter("IDX"."NAME"='T_IDX')
   9 - filter(BITAND("IC"."SPARE1",1)=1)
  10 - access("IC"."OBJ#"="IDX"."OBJ#")
  11 - filter("BASE"."NAMESPACE"=1 OR "BASE"."NAMESPACE"=5)
  12 - access("IC"."BO#"="BASE"."OBJ#" AND "BASE"."OWNER#"=USERENV('SCHEMAID'))
  14 - access("C"."OBJ#"="BASE"."OBJ#" AND "IC"."INTCOL#"="C"."INTCOL#")
  15 - access("IDX"."OBJ#"="I"."OBJ#")
  16 - filter(("I"."TYPE#"=1 OR "I"."TYPE#"=2 OR "I"."TYPE#"=3 OR "I"."TYPE#"=4
OR "I"."TYPE#"=6
              OR "I"."TYPE#"=7 OR "I"."TYPE#"=9) AND BITAND("I"."PROPERTY",1024)
=0)
  22 - access("IDX"."OWNER#"=USERENV('SCHEMAID') AND "IDX"."NAME"='T_IDX' AND "I
DX"."NAMESPACE"=4)
  23 - filter(("I"."TYPE#"=1 OR "I"."TYPE#"=2 OR "I"."TYPE#"=3 OR "I"."TYPE#"=4
OR "I"."TYPE#"=6
              OR "I"."TYPE#"=7 OR "I"."TYPE#"=9) AND BITAND("I"."PROPERTY",1024)
=0)
  24 - access("IDX"."OBJ#"="I"."OBJ#")
  25 - access("I"."BO#"="BASE"."OBJ#")
       filter("BASE"."OWNER#"<>USERENV('SCHEMAID'))
  27 - access("C"."OBJ#"="BASE"."OBJ#")
  28 - access("IC"."OBJ#"="IDX"."OBJ#")
  29 - filter(BITAND("IC"."SPARE1",1)=1 AND "IC"."INTCOL#"="C"."INTCOL#")

在以前的版本中,对此的解决办法有以下几种:
使用DECODE而不是CASE,因为DECODE不会被重写,即所谓的“所见即所得”。
使用最先搜索到的CASE语法(预计到可能会发生的优化)。
但是,倘若优化器没有使用你的基于函数的索引,可以检查USER_IND_EXPRESSIONS视图,验证使用的函数是否正确。

6 关于ORA-01743的警告

对于基于函数的索引,如果你要在内置函数TO_DATE上创建一个索引,某些情况下并不能成功创建,例如:

scott@ORCL>create table t ( year varchar2(4) );

表已创建。

scott@ORCL>create index t_idx on t( to_date(year,'YYYY') );
create index t_idx on t( to_date(year,'YYYY') )
                                 *
第 1 行出现错误:
ORA-01743: 仅能编制纯函数的索引

这看上去很奇怪,因为有时使用TO_DATE创建索引确实是可以的,例如:

scott@ORCL>create index t_idx on t( to_date('01'||year,'MMYYYY') );

索引已创建。

要在一行基于函数的索引中使用TO_DATE,必须使用一种无歧义的确定性日期格式,而不论当前是哪一天。

7 基于函数的索引小结

基于函数的索引很容易使用和实现,他们能提供立即值。可以用基于函数的索引来加快现有应用的速度,而不用修改应用中的任何逻辑或查询。通过使用基于函数的索 引,可以观察到性能会呈数量级地增长。使用这种索引能提前计算出复杂的值,而无需使用触发器。另外,如果在基于函数的索引中物化表达式,优化器就能更准确度估计出选择性。可以使用基于函数的索引有选择地只对感兴趣的几行建立索引(如前面关于PROCESSED_FLAG的例子所示)。实际上,使用这种就是可以对WHERE子句加索引。最后,我们研究了如何使用基于函数的索引来实现某种完整性约束:有选择的惟一性(例如,“每个条件成立时字段X、Y和Z必须惟一”)。
基于函数的索引会影响插入和更新的性能。如果你总是插入数据,而不经常查询,基于函数的索引可能对你并不适用。另 一方面,要记住,一般插入时都是一次插入一行,查询却会完成数千次。

转载于:https://my.oschina.net/u/1862478/blog/1920954

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值