【问题标题】:optimize sql query [closed]优化sql查询[关闭]
【发布时间】:2011-08-08 16:43:10
【问题描述】:

我的程序核心中有一个巨大的遗留查询, 查询花费了太多时间,让它运行得更快的最佳方法是什么? 我用的是 oracle 11g

 SELECT   *
     FROM     ( SELECT  COUNT(*) AS countme,
                       string_value        ,
                       name                ,
                       property_id         ,
                       category_id
              FROM    ( SELECT DISTINCT a.string_value,
                                        a.name        ,
                                        a.property_id ,
                                        b.product_id  ,
                                        a.category_id
                       FROM             filter_criterias a
                                        JOIN product_properties b
                                        ON              (
                                                                          a.property_id = b.property_id
                                                         AND
                                                                          (
                                                                                           (
                                                                                                            isnumber(b.value)        IS NOT NULL
                                                                                           AND              isnumber(a.range_bottom) IS NOT NULL
                                                                                           AND              isnumber(a.range_top)    IS NOT NULL
                                                                                           AND
                                                                                                            (
                                                                                                                             a.range_bottom >a.range_top
                                                                                                            AND              b.value       >= a.range_bottom
                                                                                                            OR               a.range_bottom<=a.range_top
                                                                                                            AND              b.value       >= a.range_bottom
                                                                                                            AND              b.value       <=a.range_top
                                                                                                            )
                                                                                           )
                                                                          )
                                                         )
                                        JOIN PRODUCT_CATEGORY prc
                                        ON               (
                                                                          prc.sku         = b.product_id
                                                         AND              prc.category_id = a.category_id
                                                         )
                                        JOIN PRODUCT pr
                                        ON               (
                                                                          b.product_id = pr.SKU
                                                         AND              pr.visible   = '1'
                                                         )
                       )
              GROUP BY (string_value, name, property_id,category_id)

              UNION

              SELECT   COUNT(*) AS countme,
                       string_value       ,
                       name               ,
                       property_id        ,
                       category_id
              FROM    ( SELECT DISTINCT a.string_value,
                                        a.name        ,
                                        a.property_id ,
                                        b.product_id  ,
                                        a.category_id
                       FROM             filter_criterias a
                                        JOIN product_properties b
                                        ON              (
                                                                          a.property_id = b.property_id
                                                         AND
                                                                          (
                                                                                           (
                                                                                                            a.name= b.value
                                                                                           )
                                                                          )
                                                         )
                                        JOIN PRODUCT_CATEGORY prc
                                        ON               (
                                                                          prc.sku         = b.product_id
                                                         AND              prc.category_id = a.category_id
                                                         )
                                        JOIN PRODUCT pr
                                        ON               (
                                                                          b.product_id = pr.SKU
                                                         AND              pr.visible   = '1'
                                                         )
                       )
              GROUP BY (string_value, name, property_id,category_id)
              )
     ORDER BY 5,4,3,2

这是解释计划

    "Optimizer" "Cost"  "Cardinality"   "Bytes" "Partition Start"   "Partition Stop"    "Partition Id"  "ACCESS PREDICATES" "FILTER PREDICATES"
"SELECT STATEMENT"  "ALL_ROWS"      "1298"  "2"         "542"   ""  ""  ""  ""  ""
"SORT(ORDER BY)"    ""              "1298"  "2"         "542"   ""  ""  ""  ""  ""
"VIEW"              ""              "1297"  "2"         "542"   ""  ""  ""  ""  ""
"SORT(UNIQUE)"      ""              "1297"  "2"         "74"    ""  ""  ""  ""  ""
"UNION-ALL" ""      ""              ""  ""  ""          ""  ""  ""  ""
"HASH(GROUP BY)"    ""              "661"   "1"         "37"    ""  ""  ""  ""  ""
"VIEW"              ""              "659"   "1"         "37"    ""  ""  ""  ""  ""
"HASH(UNIQUE)"      ""              "659"   "1"         "95"    ""  ""  ""  ""  ""
"NESTED LOOPS"      ""              ""  ""  ""          ""  ""  ""  ""  ""
"NESTED LOOPS"      ""              "658"   "1"         "95"    ""  ""  ""  ""  ""
"HASH JOIN"         ""              "493"   "1"         "81"    ""  ""  ""  ""B"."PRODUCT_ID"=TO_NUMBER("PRC"."SKU") AND "A"."CATEGORY_ID"=SYS_OP_C2C("PRC"."CATEGORY_ID")" ""
"HASH JOIN"         ""              "369"   "2"         "128"   ""  ""  ""  ""B"."PROPERTY_ID"=TO_NUMBER("A"."PROPERTY_ID")"    ""A"."RANGE_BOTTOM">"A"."RANGE_TOP" AND "A"."RANGE_BOTTOM"<=TO_NUMBER("B"."VALUE") OR "A"."RANGE_BOTTOM"<="A"."RANGE_TOP" AND "A"."RANGE_BOTTOM"<=TO_NUMBER("B"."VALUE") AND "A"."RANGE_TOP">=TO_NUMBER("B"."VALUE")"
"TABLE ACCESS(FULL) BNET.B_FILTER_CRITERIAS"    "ANALYZED"  "36"    "28"    "1148"  ""  ""  ""  ""  ""ISNUMBER"(TO_CHAR("A"."RANGE_BOTTOM")) IS NOT NULL AND "ISNUMBER"(TO_CHAR("A"."RANGE_TOP")) IS NOT NULL"
"TABLE ACCESS(FULL) BNET.B_PRODUCT_PROPERTIES"  "ANALYZED"  "332"   "12566" "289018"    ""  ""  ""  ""  ""ISNUMBER"("B"."VALUE") IS NOT NULL"
"TABLE ACCESS(FULL) BNET.WLCS_PRODUCT_CATEGORY" "ANALYZED"  "124"   "129762"    "2205954"   ""  ""  ""  ""  ""
"INDEX(RANGE SCAN) BNET.WLCS_PROD_VISIBLE_IDX"  "ANALYZED"  "12"    "6208"  ""  ""  ""  ""  ""PR"."VISIBLE"='1'"    ""
"TABLE ACCESS(BY INDEX ROWID) BNET.WLCS_PRODUCT"    "ANALYZED"  "164"   "1" "14"    ""  ""  ""  ""  ""B"."PRODUCT_ID"=TO_NUMBER("PR"."SKU")"
"HASH(GROUP BY)"    ""              "637"   "1"         "37"    ""  ""  ""  ""  ""
"VIEW"              ""              "635"   "1"         "37"    ""  ""  ""  ""  ""
"HASH(UNIQUE)"      ""              "635"   "1"         "91"    ""  ""  ""  ""  ""
"HASH JOIN"         ""              "634"   "1"         "91"    ""  ""  ""  ""B"."PRODUCT_ID"=TO_NUMBER("PRC"."SKU") AND "A"."CATEGORY_ID"=SYS_OP_C2C("PRC"."CATEGORY_ID")" ""
"NESTED LOOPS"      ""              ""      ""  ""  ""  ""  ""  ""  ""
"NESTED LOOPS"      ""              "509"   "1"         "74"    ""  ""  ""  ""  ""
"HASH JOIN"         ""              "345"   "1"         "60"    ""  ""  ""  ""B"."PROPERTY_ID"=TO_NUMBER("A"."PROPERTY_ID") AND "A"."NAME"="B"."VALUE"" ""
"TABLE ACCESS(FULL) BNET.B_FILTER_CRITERIAS"    "ANALYZED"  "35"    "11257" "416509"    ""  ""  ""  ""  ""
"TABLE ACCESS(FULL) BNET.B_PRODUCT_PROPERTIES"  "ANALYZED"  "309"   "251319"    "5780337"   ""  ""  ""  ""  ""
"INDEX(RANGE SCAN) BNET.WLCS_PROD_VISIBLE_IDX"  "ANALYZED"  "12"    "6208"  ""  ""  ""  ""  ""PR"."VISIBLE"='1'"    ""
"TABLE ACCESS(BY INDEX ROWID) BNET.WLCS_PRODUCT"    "ANALYZED"  "164"   "1" "14"    ""  ""  ""  ""  ""B"."PRODUCT_ID"=TO_NUMBER("PR"."SKU")"
"TABLE ACCESS(FULL) BNET.WLCS_PRODUCT_CATEGORY" "ANALYZED"  "124"   "129762"    "2205954"   ""  ""  ""  ""  ""

【问题讨论】:

  • 无法判断,因为我们不知道存在哪些索引。显示解释计划可能是一个好的开始...
  • @Daniel - 即使没有计划,由于 ISNUMBER,我也可以看到数值被存储为字符串。这导致了表扫描。这就像试图通过吃豆子和在显眼的地方拿着 zippo 来给热气球充气一样。
  • @Lasse V. Karlsen:我不认为这个问题过于本地化。虽然有很多细节对其他人来说并不重要,但将数字存储为字符串的核心问题适用于大量受众。

标签: sql optimization oracle11g


【解决方案1】:

一个潜在的大量问题来源是您必须使用 ISNUMBER。

如果将数值存储为文本,然后使用诸如“x - 字符串在使用前必须解析为数字
- 字符串的索引可能与数字的索引没有相似之处
- 如果索引没用,你会得到表扫描而不是索引搜索

我强烈建议将值存储为实数,而不是字符串。不必使用 ISNUMBER,不必转换每个值,因此实际上能够使用索引可以带来极大的性能优势。

编辑

您刚刚添加的 PLAN 包含许多 TABLE ACCESS(FULL) 实例,其中一些似乎与存储为字符串的数值相关联。

【讨论】:

    【解决方案2】:

    我会首先通过至少缩进让它更具可读性。如果你看不懂它,你就无法优化它。您可以通过使用与表名匹配的别名使其更具可读性,因此filter_criterias 变为fc 而不是a。在下面的查询中,我只是稍微修正了大纲并去掉了多余的括号。

    SELECT   
      *
    FROM  
    ( 
        SELECT  
            COUNT(*) AS countme,
            string_value,
            name,
            property_id,
            category_id
        FROM
        (
            SELECT DISTINCT 
                a.string_value,
                a.name,
                a.property_id,
                b.product_id,
                a.category_id
            FROM
                filter_criterias a
                INNER JOIN product_properties b
                    ON a.property_id = b.property_id
                    AND isnumber(b.value) IS NOT NULL
                    AND isnumber(a.range_bottom) IS NOT NULL
                    AND isnumber(a.range_top) IS NOT NULL
                    AND ( 
                        a.range_bottom > a.range_top
                        AND b.value >= a.range_bottom
                        OR a.range_bottom <= a.range_top
                        AND b.value >= a.range_bottom
                        AND b.value <=a.range_top
                    )
                INNER JOIN PRODUCT_CATEGORY prc
                    ON prc.sku = b.product_id
                    AND prc.category_id = a.category_id
                INNER JOIN PRODUCT pr
                    ON b.product_id = pr.SKU
                    AND pr.visible = '1'
        )
    GROUP BY
        string_value, 
        name, 
        property_id,
        category_id
    
    UNION
    
    SELECT 
        COUNT(*) AS countme,
        string_value,
        name,
        property_id,
        category_id
    FROM
        (
        SELECT DISTINCT 
            a.string_value,
            a.name     ,
            a.property_id ,
            b.product_id  ,
            a.category_id
        FROM
            filter_criterias a
            INNER JOIN product_properties b
                ON a.property_id = b.property_id
                AND a.name = b.value
            INNER JOIN PRODUCT_CATEGORY prc
                ON prc.sku = b.product_id
                AND prc.category_id = a.category_id
            INNER JOIN PRODUCT pr
                ON b.product_id = pr.SKU
                AND pr.visible = '1'
        )
    GROUP BY
        string_value, 
        name, 
        property_id,
        category_id
    ORDER BY 5,4,3,2
    

    完成此操作后,您会注意到它包含查询,由 UNION 分隔。如果这些查询每个都包含不同的行,则可以使用 UNION ALL。只需 UNION,将对结果执行另一个 DISTINCT,速度较慢。

    此外,这两个子选择几乎相同,除了 product_properties b 的连接中的单个条件(通过将每个子选择放入 WinMerge 或类似工具进行检查)。因此,也许您可​​以完全跳过联合,并在连接中将两个条件组合到 OR 中,尽管您必须记住 OR 会减慢连接速度!

    当您查看查询的解释计划时,会出现这类问题。查看它以了解哪些连接会给您带来问题总是很好的。有时它只是一个被遗忘的索引。但重要的是要知道某些操作会减慢查询速度,例如在连接中使用 OR(您会这样做)、在不需要时使用 DISTINCT 以及在可以使用 UNION ALL 的情况下使用 UNION。

    【讨论】:

      【解决方案3】:

      逐步检查执行计划并查看瓶颈在哪里。最重要的是,您可以查看以下几点:

      • 不要使用 SELECT * - 选择您需要的特定列。
      • 检查联接,看看是否有任何方法可以提高它们的效率
      • 尽可能用派生表替换嵌套查询
      • 在查询顶部使用 SET NOCOUNT ON
      • 确保所有表都正确编入索引

      您肯定需要查看执行计划,然后从那里开始。

      【讨论】:

      • SET NOCOUNT ON 在 Oracle 中???
      【解决方案4】:

      在解释计划中,您首先应该查看的是基数(估计的行数)。返回 1 行的最优计划通常与返回 10 亿行的最优计划非常不同。如果 Oracle 的估计严重错误,您需要尝试找出错误的原因以及您可以采取的措施。

      我同意@Dems 的观点,即 ISNUMBER 可能是您的问题的原因,但原因不同。 Oracle 无法准确猜测使用自定义函数的谓词会过滤掉多少行。尽管您可能知道 99.9% 的行会通过该过滤器,但 Oracle 假设只有 5% 会通过。这会导致基数非常低,从而导致效率低下的嵌套循环而不是哈希连接。

      您可以通过在 ISNUMBER 函数上创建扩展统计信息来为优化器提供更多有用的信息。这假设您使用的是 Oracle 11g,并且 ISNUMBER 是确定性的:

      select dbms_stats.create_extended_stats(null,'product_properties','(isnumber(value))') from dual;
      select dbms_stats.create_extended_stats(null,'filter_criterias','(isnumber(range_bottom))') from dual;
      select dbms_stats.create_extended_stats(null,'filter_criterias','(isnumber(range_top))') from dual;
      
      --Must re-gather table stats for the extended stats to work
      begin
          dbms_stats.gather_Table_stats(user, 'product_properties', no_invalidate => false);
          dbms_stats.gather_Table_stats(user, 'filter_criterias', no_invalidate => false);
      end;
      /
      

      但是,您的第二个查询没有使用 ISNUMBER,并且估计的基数仍然为 1。您的表和索引统计信息是最新的吗?检查select last_analyzed, table_name from user_tables;。或者甲骨文可能永远无法正确估算。 /*+ no_use_nl(a b prc pr) */ 之类的提示可能会有所帮助。

      此外,您似乎正在尝试在 SQL 中实现短路逻辑,但这并不总是有效。 Oracle 不一定会从上到下处理谓词,您可能会发现,当计划更改时,您的查询会失败。

      【讨论】:

        【解决方案5】:

        一些提示:

        1. 确保您创建了正确的索引并且是最新的
        2. 确保不执行隐式类型转换(pr.visible = '1' 看起来就是这样一种情况)

        【讨论】:

          猜你喜欢
          • 2022-08-19
          • 2014-07-13
          • 1970-01-01
          • 2011-04-13
          • 2017-10-12
          • 2010-09-20
          • 2013-10-07
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多