在where子句中使用case语句时,Oracle存储过程未使用索引

时间:2018-08-17 21:07:25

标签: oracle performance indexing query-optimization oracle12c

我需要查询一个表,并且可以根据前端选择的选项在WHERE子句中传递一个或最多六个参数。我在WHERE子句中使用了CASE语句,以便处理所有排列。

这是SP代码段:

create procedure return_data ( 
     p_field1 in varchar(20), 
     p_field2 in varchar2(30), 
     p_field3 in varchar2(30), 
     cur out sys_refcursor)
is 
begin
    open cur for
        select col1, col2, col3,col4,col5,col6 
        from master_table
        where (case when (p_field1 is null) then (1) 
                   when (p_field1 is not null) and (col1=p_field1) then 1 
                   else 0 end) =1 
       and (case when (p_field2 is null) then (1) 
                 when (p_field2 is not null) and (col2=p_field2) then 1 
                 else 0 end) =1 
      ... so one repeat for all columns.

但是,master_table有超过500万行,因此我在每个搜索字段上创建了一个索引,但是存储过程未使用该索引,而是在进行全表扫描,从而导致性能降低。

如果我从WHERE子句中删除CASE语句并传递类似where col1=p_field1)的内容,则使用索引,并且查询性能非常好。

但是,由于并非所有输入字段都是前端必填的,因此我必须在WHERE子句中使用CASE语句。

准确地说,在WHERE子句中使用CASE语句时,Oracle没有使用索引。

有人可以建议如何调整上述sql,以便它使用索引来提高性能吗?

谢谢。

1 个答案:

答案 0 :(得分:4)

您编写了一个查询,实际上是64(2 ^ 6)个查询。参数的不同排列将适合不同的访问路径。

优化器根据其对表的所有了解为查询创建特定的访问路径。例如,在WHERE子句中使用这些列的选择性如何?

  • 任何列都具有唯一索引吗?这对索引查找很有用。
  • 直方图是否表示一列只有三个值,在行中平均分配?这对索引查找不利。
  • 聚类因子如何?如果索引列的值分布在每个表块中,则使用全表扫描比读取索引更有效。
  • 此列是否高度倾斜,以便一个或两个值构成大多数条目?对于这两个值,索引读取将是灾难性的,但对于其他值的长尾则非常有效。

这就是您设置优化器的挑战:提出一个单一的执行计划,该计划将适用于用户传递的任何参数排列。当然不能那样做。遇到的问题是,只有一个参数置换可以通过全表扫描来服务,而这六个参数全部为空。 (您说存储过程每次都至少传递一个参数,但这是在查询外部强制执行的,因此优化器不知道这一点。)此外,某些索引列可能没有那么选择,以致造成灾难性的后果。因此,优化程序似乎选择使用全表扫描作为所有可能排列中危险最小的方法,即使最终对于大多数排列而言效率不高。

该怎么办?

一种方法是要求优化器在每次运行时重新解析查询。有complicated ways of doing this (mis-)using Row Level Security,但是您可能需要做的就是在查询中应用/*+ BIND_AWARE */提示。

或者,使用动态SQL。从字面上编写不同的SQL语句,具体取决于填充的参数。像这样

...
is 
   stmt varchar2(32767);
begin
    stmt := '
        select col1, col2, col3,col4,col5,col6 
        from master_table
        where 1=1';
    if p_field1 is not null then
        stmt := stmt || ' and col1 = '''|| p_field1 ||'''';
    end if;
    if p_field2 is not null then
        stmt := stmt || ' and col2 = '''|| p_field2 ||'''';
    end if;
    if p_field3 is not null then
        stmt := stmt || ' and col3 = '''|| p_field3 ||'''';
    end if;
    open cur for stmt;

注意:在上面的代码中,我选择不使用绑定变量。我这样做的主要原因是需要在OPEN ..,USING语句中对参数进行64个排列。但是,@ WilliamRobertson已建议链接到Tom Kyte的文章,该文章具有巧妙的处理方式。 Find out more

这是我按照汤姆·凯特(Tom Kyte)的代码写的代码:

...
is 
   stmt varchar2(32767);
begin
    stmt := '
        select col1, col2, col3,col4,col5,col6 
        from master_table';
    if p_field1 is not null then
        stmt := stmt || ' where col1 = :p_field1 ';
    else 
        stmt := stmt || ' where (1 = 1 or :p_field1 is null)';
    end if;
    if p_field2 is not null then
        stmt := stmt || ' and col2 =:p_field2 ';
    else 
        stmt := stmt || ' and (1 = 1 or :p_field2 is null)';
    end if;
    if p_field3 is not null then
        stmt := stmt || ' and col3 = :p_field3 ';
    else 
        stmt := stmt || ' and (1 = 1 or :p_field3 is null)';
    end if;
    open cur for stmt
        using p_field1, p_field2, p_field3;