【问题标题】:Force SQL subquery to be run first强制 SQL 子查询首先运行
【发布时间】:2018-01-02 01:13:03
【问题描述】:

我有一个这样的(简化的)查询:

SELECT DISTINCT this_.person_id AS y0_
  FROM coded_entity this_
 WHERE this_.code     = ?
   AND this_.tenant_id  = ?
   AND this_.person_id IN (SELECT person_id
                             FROM person that
                            WHERE that.date_voided IS NULL
                              AND that.ssn            = ?
                              AND that.tenant_id      = ? ) 

我希望能够强制 Oracle 始终首先执行最里面的子查询,因为我知道它总是更具选择性。代码只有几个值,而 ssn 的值要多得多。每个表的行数都相同。

但是,在实际实践中,有时 Oracle CBO 决定先执行 coded_entity 查询,从而导致时间慢得多。

有没有办法在不将查询拆分为单独调用的情况下强制执行此行为?

【问题讨论】:

  • 您是否考虑过使用基数提示?
  • 收集数据,Oracle没那么傻,exec计划不稳定一定是有原因的。否则,作为最后的手段,您可以使用 CARNALITY、MATERIALIZE 或 NO_PUSH_PRED 提示
  • “person_id”列是person表的主键吗?
  • 恕我直言,您应该从 GATHER_PLAN_STATISTICS 开始推断优化估计错误的地方。
  • 顺便说一句,jonathan lewis 做了一系列关于阅读/分析执行计划和这个特定条目 allthingsoracle.com/execution-plans-part-6-pushed-subqueries 的系列文章,他在其中介绍了两个查询提示,其意图与您相同:/*+ no_unnest push_subq * /

标签: sql database oracle sql-execution-plan sql-optimization


【解决方案1】:

您始终可以重写脚本,让条件首先作为子选择应用,然后在此之上应用附加条件,如下所示:

SELECT DISTINCT this_.person_id AS y0_
FROM (
SELECT *
  FROM coded_entity this_
 WHERE this_.person_id IN (SELECT person_id
                             FROM person that
                            WHERE that.date_voided IS NULL
                              AND that.ssn            = ?
                              AND that.tenant_id      = ? ) 
) sub
where this_.code     = ?
   AND this_.tenant_id  = ?

或者你可以改写成join格式:

SELECT DISTINCT this_.person_id AS y0_
  FROM coded_entity this_
  JOIN person that
    ON that.person_id = this_person_id
    AND that.date_voided IS NULL
    AND that.ssn            = ?
    AND that.tenant_id      = ?
 WHERE this_.code     = ?
   AND this_.tenant_id  = ?

或者我认为肯定会起作用的方法是正确收集两个表的统计数据,然后 CBO 将做出正确决定。

【讨论】:

    【解决方案2】:

    不需要子查询。只需INNER JOIN 并过滤唯一记录。让优化器更好地解决它。

    SELECT DISTINCT this_.person_id AS y0_
    FROM coded_entity this_
    INNER JOIN person that ON this_.person_id = that.person_id
        AND this_.tenant_id = that.tenant_id
        AND that.date_voided IS NULL
        AND that.ssn = ?
     WHERE this_.code     = ?
       AND this_.tenant_id  = ?
    

    【讨论】:

    • 看,这实际上是我原来的查询,我正在重写。而且大部分时间都运行得很好。然而,每隔一段时间,就会导致笛卡尔合并,从而导致查询时间过长——有效地破坏了事情。我们的 DBA 从未真正找到原因,所以这是我试图摆脱可能导致加入情况的事情的尝试。
    • 有趣。他们无法告诉您哪些数据输入导致了长连接?我在那里看不到任何会导致奇怪的 JOIN 的东西。数据库中的坏数据,也许?还是不稳定的索引?
    • 实际上,如果您正在处理大量数据,您可以通过将 JOIN 的 AND this_.tenant_id = that.tenant_id 改回 AND that.tenant_id = ? 来缩短它。
    • 是的,我们的 DBA 并没有很热衷于调查……只是不断重复“重写查询!”。这很令人沮丧。我认为统计数据/直方图存在问题(他们关闭了所有直方图),但我真的没有知识可说。
    【解决方案3】:

    我的第一个想法(不管键)是试试这个:

    SELECT DISTINCT this_.person_id AS y0_
      FROM coded_entity this_
     WHERE this_.code     = ?
       AND this_.tenant_id  = ?
       AND EXISTS (SELECT null
                     FROM person THAT_
                    WHERE THAT_.date_voided IS NULL
                      AND THAT_.ssn            = ?
                      AND THAT_.tenant_id      = this_.tenant_id
                      AND THAT_.person_id      = this_.person_id) 
    

    但更好的方法(如果 person_id 是 person 的键并且每个人都有零个或多个编码实体)是这样的:

    SELECT this_.person_id AS y0_
      FROM person_ this_
     WHERE this_.ssn        = ?
       AND this_.tenant_id  = ?
       AND this_.date_voided is null
       AND EXISTS (SELECT null
                     FROM coded_entity THAT_
                    WHERE THAT_.code = ?
                      AND THAT_.tenant_id      = this_.tenant_id
                      AND THAT_.person_id      = this_.person_id) 
    

    【讨论】:

    • 我会尝试一下,看看它的表现如何。谢谢。
    猜你喜欢
    • 2013-01-16
    • 2012-03-11
    • 1970-01-01
    • 2012-06-06
    • 2010-12-18
    • 2012-11-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多