【问题标题】:SMART Logic to avoid dynamic SQL and indexes usageSMART Logic 避免使用动态 SQL 和索引
【发布时间】:2019-01-12 14:25:45
【问题描述】:

假设我们有一个 EMPLOYEE 表,我们要使用以下字段(我们在其上建立索引)上的三个过滤器进行查询:subsidy_id、employee_id、last_name。

如果我们在where子句中使用简单的过滤器和参数绑定的动态SQL构造查询,例如:WHERE last_name = :name,则使用索引并且响应快。

现在的问题是,如果我们在查询中使用 SMART Logic 来用静态 SQL 构造查询,这样:

SELECT subsidiary_id, employee_id, last_name
FROM EMPLOYEE 
WHERE (:sub_id IS NULL OR subsidiary_id = :sub_id)
AND   (:emp_id IS NULL OR employee_id = :emp_id)
AND   (:name IS NULL OR last_name = :name)

即使执行了查询,并且由于所有可能的过滤器表达式都在语句中静态编码,因此避免了使用动态 SQL 的需要,但由于数据库(Oracle)无法优化执行计划,因此会导致反模式对于一个特定的过滤器(因为它们中的任何一个都可以在运行时被取消),它必须为最坏的情况(所有过滤器被禁用)执行全表扫描做准备,即使每列都有一个索引用于过滤器。

问题是:如果将带有智能逻辑的查询放在存储过程/函数中会发生什么? 数据库是否足够智能以使用索引或执行全表扫描,如使用绑定参数提交的查询?

Oracle 存储过程正文:

create procedure myproc (employee_id IN NUMBER, subsidiary_id IN NUMBER, name IN VARCHAR2, prc out sys_refcursor)
is
begin
     open prc for (SELECT e.subsidiary_id, e.employee_id, e.last_name
                    FROM EMPLOYEE e
                    WHERE (subsidiary_id IS NULL OR e.subsidiary_id = subsidiary_id)
                    AND   (employee_id IS NULL OR e.employee_id = employee_id)
                    AND   (name IS NULL OR e.last_name = name)
                );
end;

如果在存储过程或函数中执行,查询是否使用索引?

【问题讨论】:

  • 相同的查询使用相同的解释计划,无论是从存储过程调用还是在 SQL*Plus(或其他)中运行。
  • 运行你的程序并使用来自该线程的提示检查解释计划:asktom.oracle.com/pls/apex/…,你会看到。

标签: database oracle indexing


【解决方案1】:

退一步说,如果查询要运行几十/百/千次,查询优化器应该确定多少次计划。

为每次执行进行优化肯定是低效的。 Oracle 曾经对每个语句优化一次(如果会话更改了默认优化器、NLS、排序规则设置等,则有一些例外)

在 11g 中,它提出了自适应光标共享,它会尝试查看不同的计划对于不同的查询参数是否会更好。如果它最初选择了一个计划,但发现后续查询与计划中的假设不匹配,它可以切换到另一个计划。

https://oracle-base.com/articles/11g/adaptive-cursor-sharing-11gr1

我的建议是不要依赖这个。为您可以确信会有合适索引的最“预期”路径显式编码查询。您正在构建一个应用程序,该应用程序有权获得超出提供即席查询的期望。

并始终使用命名约定来确保您的 PL/SQL 变量/参数名称不会与列名称混淆。

create procedure myproc (p_employee_id IN NUMBER, p_subsidiary_id IN NUMBER, p_name IN VARCHAR2, prc out sys_refcursor)
is
begin
    if p_employee_id is not null then
     open prc for (SELECT e.subsidiary_id, e.employee_id, e.last_name
                    FROM EMPLOYEE e
                    WHERE (p_subsidiary_id IS NULL OR e.subsidiary_id = p_subsidiary_id)
                    AND   e.employee_id = p_employee_id
                    AND   (p_name IS NULL OR e.last_name = p_name)
                );
    elsif p_name is not null
     open prc for (SELECT e.subsidiary_id, e.employee_id, e.last_name
                    FROM EMPLOYEE e
                    WHERE (p_subsidiary_id IS NULL OR e.subsidiary_id = p_subsidiary_id)
                    AND   (p_employee_id IS NULL OR e.employee_id = p_employee_id)
                    AND   e.last_name = p_name
                );
    elsif p_subsidiary_id is not null
     open prc for (SELECT e.subsidiary_id, e.employee_id, e.last_name
                    FROM EMPLOYEE e
                    WHERE e.subsidiary_id = p_subsidiary_id
                    AND   (p_employee_id IS NULL OR e.employee_id = p_employee_id)
                    AND   (p_name IS NULL OR e.last_name = p_name)
                );
    else
     open prc for (SELECT e.subsidiary_id, e.employee_id, e.last_name
                    FROM EMPLOYEE e
                );
    end if;
end;

【讨论】:

    【解决方案2】:

    根据我的查询完全按照您上面显示的操作的经验,您最好的选择是动态构建查询以避免(:sub_id IS NULL OR subsidiary_id = :sub_id) 类型的逻辑。您可以尝试使用NVL(:sub_id, subsidiary_id) = subsidiary_id,但总的来说我还没有发现它可以提供良好的性能。我发现动态执行游标的性能比您展示的逻辑要好得多,即使有适当的索引也是如此。

    祝你好运。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-09-07
      • 2012-10-07
      • 2014-12-22
      • 2013-12-27
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多