【问题标题】:Execute Subquery refactoring first before any other SQL在任何其他 SQL 之前先执行子查询重构
【发布时间】:2019-09-01 21:08:24
【问题描述】:

我有一个非常复杂的视图,格式如下

create or replace view loan_vw as 
select * from (with loan_info as (select loan_table.*,commission_table.* 
                                   from loan_table,
                                  commission_table where 
                                  contract_id=commission_id)
                select /*complex transformations */ from loan_info
                where type <> 'PRINCIPAL'
                union all 
                select /*complex transformations */ from loan_info
                where type = 'PRINCIPAL')

现在如果我执行以下操作,则选择查询挂起

         select * from loan_vw where contract_id='HA001234TY56';

但是,如果我在子查询重构中硬编码或在同一会话中使用包级变量,查询会在一秒钟内返回

create or replace view loan_vw as 
        select * from (with loan_info as (select loan_table.*,commission_table.* 
                                           from loan_table,
                                          commission_table where 
                                          contract_id=commission_id
                                          and contract_id='HA001234TY56'
                                          )
                        select /*complex transformations */ from loan_info
                        where type <> 'PRINCIPAL'
                        union all 
                        select /*complex transformations */ from loan_info
                        where type = 'PRINCIPAL')

由于我使用业务对象,我不能使用包级变量

所以我的问题是Oracle中有一个提示告诉优化器首先检查子查询重构中loan_vw中的contract_id

根据要求,使用的分析函数如下

select value_date, item, credit_entry, item_paid
from (
  select value_date, item, credit_entry, debit_entry,
    greatest(0, least(credit_entry, nvl(sum(debit_entry) over (), 0)
      - nvl(sum(credit_entry) over (order by value_date
          rows between unbounded preceding and 1 preceding), 0))) as item_paid
  from your_table
)
where item is not null;

在遵循 Boneist 和 MarcinJ 的建议后,我删除了子查询重构 (CTE),并编写了一个长查询,如下所示,将性能从 3 分钟提高到 0.156 秒

  create or replace view loan_vw as
  select /*complex transformations */
                               from loan_table,
                              commission_table where 
                              contract_id=commission_id
               and loan_table.type <> 'PRINCIPAL'
  union all
  select /*complex transformations */
                               from loan_table,
                              commission_table where 
                              contract_id=commission_id
               and loan_table.type = 'PRINCIPAL'

【问题讨论】:

  • 我认为你在 contract_id 列上有索引,因此它在硬编码时得到了更快的优化
  • yes index is there on contract_id
  • 所以在创建视图时它挂起,因为当您从视图中查询时,优化器没有可用的索引工具,所以它变慢或挂起基于它显示的数据量行为
  • 是的,基于它挂起的数据量,但由于我正在传递contract_id,我希望它首先查看loan_info
  • 尝试使用可能对您有所帮助的物化视图docs.oracle.com/cd/A97630_01/server.920/a96567/repmview.htm

标签: sql oracle performance subquery query-optimization


【解决方案1】:

这些转换真的那么复杂,你必须使用UNION ALL吗?优化你看不到的东西真的很难,但你是否尝试过摆脱 CTE 并内联实现你的计算?

CREATE OR REPLACE VIEW loan_vw AS
SELECT loan.contract_id
     , CASE commission.type -- or wherever this comes from
         WHEN 'PRINCIPAL'
         THEN SUM(whatever) OVER (PARTITION BY loan.contract_id, loan.type) -- total_whatever

         ELSE SUM(something_else) OVER (PARTITION BY loan.contract_id, loan.type) -- total_something_else
      END AS whatever_something
  FROM loan_table loan 
 INNER 
  JOIN commission_table commission
    ON loan.contract_id = commission.commission_id

请注意,如果您的分析函数没有 PARTITION BY contract_id,您将根本无法在该 contract_id 列上使用索引。

Take a look at this db fiddle(您必须单击最后一个结果表上的... 以展开结果)。在这里,loan 表有一个索引 (PK) contract_id 列,还有 some_other_id,它也是唯一的,但没有索引,外部查询的谓词仍在 contract_id 上。如果您比较partition by contractpartition by other id 的计划,您会发现partition by other id 计划中根本没有使用索引:与@ 相比,贷款表上有TABLE ACCESSFULL 选项987654336@ - UNIQUE SCANpartition by contract。这显然是因为优化器无法自行解决contract_idsome_other_id 之间的关系,因此它需要在整个窗口上运行SUMAVG,而不是通过使用索引来限制窗口行数。

如果您有一个包含这些合同的维度表,您还可以尝试将其加入您的结果并从维度表中公开contract_id,而不是最有可能的巨额贷款事实表。有时,这可以通过在维度表上使用唯一索引来改进基数估计。

同样,如果没有查询甚至没有计划,优化黑盒真的很困难,所以我们不知道发生了什么。例如,CTE 或子查询可能会不必要地具体化。

【讨论】:

  • 根据您的建议编写查询解决了问题
【解决方案2】:

感谢更新以包含列列表示例。

鉴于您更新的查询,我建议更改您的视图(或者可能创建第二个视图来查询单个contract_id,如果您的原始视图可用于查询多个contract_id - 当然,除非原始视图的结果仅对单个contract_ids有意义!)类似于:

CREATE OR REPLACE VIEW loan_vw AS 
WITH loan_info AS (SELECT l.*, c.* -- for future-proofing, you should list the column names explicitly; if this statement is rerun and there's a column with the same name in both tables, it'll fail.
                   FROM   loan_table l
                          INNER JOIN commission_table c ON l.contract_id = c.commission_id -- you should always alias the join condition columns for ease of maintenance.
                  )
SELECT value_date,
     item,
     credit_entry,
     debit_entry,
     GREATEST(0,
            LEAST(credit_entry,
                NVL(SUM(debit_entry) OVER (PARTITION BY contract_id), 0)
                  - NVL(SUM(credit_entry) OVER (PARTITION BY contract_id ORDER BY value_date ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING), 0))) AS item_paid
FROM   loan_info
WHERE  TYPE <> 'PRINCIPAL'
UNION ALL
SELECT ...
FROM   loan_info
WHERE  TYPE = 'PRINCIPAL';

请注意,我已将您的联接转换为 ANSI 语法,因为它比旧式联接更容易理解(首先,更容易将联接条件与谓词分开!)。

【讨论】:

  • 我已经通过 contract_id 进行了分区,性能从 3 分钟提高到 2 分钟,但仍然很长
  • 抱歉,我在回答中错过了partition by contract_id 之一。我假设您更新了 SUM(...) 分析函数?
  • 您是否尝试过我在第一条评论中建议的内联提示?我的猜测是,loan_info 子查询是在应用谓词之前实现的,这意味着您失去了 contract_id 上任何索引的好处。如果可行,我建议您重写查询以避免联合所有和/或分解子查询(也称为“with”子句)。请参阅 MarcinJ 对表的单一访问的答案。
  • 您最好的办法是重写查询,只访问一次 loan_info 子查询(或者直接访问根本没有子查询的基础表,根据 MarcinJ 的回答)。
  • 我删除了子查询并编写了一个长查询,结果在 0.156 秒内返回。谢谢 Boneist
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-12-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多