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

Kri*_*h T 2 oracle indexing performance 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.
Run Code Online (Sandbox Code Playgroud)

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

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

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

准确地说,对于 WHERE 子句中的 CASE 语句,Oracle 不使用索引。

有人可以建议如何调整上面的sql,以便它使用索引来获得快速性能吗?

提前致谢。

APC*_*APC 5

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

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

  • 是否有任何列具有唯一索引?这对于索引查找很有好处。
  • 直方图是否表明一列只有三个值,均匀分布在各行中?这对于索引查找来说是不好的。
  • 那么聚类因子呢?如果索引列的值分布在每个表块中,则使用全表扫描比读取索引更有效。
  • 该列是否高度倾斜,以至于一个或两个值构成了大多数条目?索引读取对于这两个值来说将是灾难性的,但对于其他值的长尾却非常有效。

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

对此该怎么办?

一种方法是要求优化器在每次运行时重新解析查询。使用行级安全性(错误地)执行此操作有多种复杂的方法 ,但也许您所需要做的就是将/*+ 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;
Run Code Online (Sandbox Code Playgroud)

注意:在上面的代码中我选择不使用绑定变量。我这样做的主要原因是需要在 OPEN .., USING 语句中对参数进行 64 种排列。然而,@WilliamRobertson 建议了一个 Tom Kyte 文章的链接,该文章有一个巧妙的方法来处理这个问题。了解更多

这是我按照 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;
Run Code Online (Sandbox Code Playgroud)