SQL Server 语句在 Oracle 中即时运行时需要永远

Kra*_*ter 1 performance sql-server execution-plan sql-server-2014 query-performance

请帮我解释这个声明和计划:

https://www.brentozar.com/pastetheplan/?id=Bysy6YtEV

我们从 Oracle 迁移到 SQL Server,我们有一些非常奇怪的行为。它可能与迁移过程中的问题有关。

我发现很难解释执行计划。两种环境都应该具有相同的结构和索引。统计数据应该是最新的。SQL Server 中的设置:

  • 启用创建自动统计
  • 针对 Ad Hoc 查询进行优化 = true
  • 启用快照隔离
  • 最大平行 = 4
  • 阈值 50

DB 大小为 600 Gb,16 核,160 Gb 内存。

在此处输入图片说明

查询:

SELECT
    COUNT ( t_01.rsecondary_objectu ) AS selectExpr
FROM
    PIMANRELATION t_01 ,
    PITEM t_02 ,
    PITEMREVISION t_03
WHERE
    (   (   (   ( t_01.rprimary_objectu = t_02.puid )
            OR  ( t_01.rprimary_objectu = t_03.puid )
            )
       AND
            ( t_01.rrelation_typeu = 'w8INy241VJFL2B' )
        )
   AND  t_01.rsecondary_objectu = '2yLJkWqiVJFL2B'
    )
Run Code Online (Sandbox Code Playgroud)

甲骨文

在此处输入图片说明

我们发现问题是相关的,并且取决于数据。如果我选择一个不同的项目在 GUI 中复制(它实际上是一个复制的东西以及应用程序如何执行这些语句),它会立即工作。查询然后一旦工作正常看起来有点不同:https : //www.brentozar.com/pastetheplan/?id=SJo-h2c44

SELECT  COUNT ( t_01.rsecondary_objectu ) AS selectExpr FROM PIMANRELATION t_01 , PITEM t_02 , PITEMREVISION t_03 WHERE ( ( ( ( t_01.rprimary_objectu = t_02.puid ) OR ( t_01.rprimary_objectu = t_03.puid ) )  AND ( t_01.rrelation_typeu = 'w8INy241VJFL2B'  ) ) AND t_01.rsecondary_objectu IN  ('wBHZpD0uVJFL2B' , 'V2PlBGlAVJFL2B' , 'lnHlBGlAVJFL2B'  ) )
Run Code Online (Sandbox Code Playgroud)

SQl Server 似乎与我之前提到的(无限运行)完全斗争,同时几乎立即提供了第二个。这太疯狂太奇怪了。就像一个有缺陷的产品。

在此处输入图片说明

我对这些语句没有影响,因为它们是由应用程序生成的。

CE 110 / 70 产生相同的计划。即使禁用所有自定义索引并仅保留应用程序建议的索引,它的行为也是相同的。所有主键都是聚集索引。但也许在我们的迁移过程中出现了一些混乱。但这很奇怪。大多数东西运行良好,但主题查询是极端的。我让它在 SQL Server 中运行了 45 分钟。它根本没有完成。


另一个例子。一旦创建了最高评级的索引,我们的 Prod 环境将无法使用:

索引:CREATE INDEX EN_PIPRELEASESTATUS_1 ON [TCEUP01].[dbo].[PRELEASESTATUS] ([pname], [pdate_released]) INCLUDE ([puid])

此查询的结果:https : //pastebin.com/Ax3qTUjd ===> 耗时 137.142 秒

我们的测试环境,应该是一样的,但表现不同:https : //pastebin.com/0PTZTJpr ===>花了 3.884 秒

有问题的计划如下所示:https : //www.brentozar.com/pastetheplan/?id=Skvy0qRNE

它似乎可以无限运行。 在此处输入图片说明

Joe*_*ish 10

Oracle 优化器使用OR 扩展来提高查询效率。从文档中引用:

在 OR 扩展中,优化器将带有包含 OR 运算符的 WHERE 子句的查询转换为使用 UNION ALL 运算符的查询。

数据库可以出于各种原因执行 OR 扩展。例如,它可以启用更有效的访问路径或避免笛卡尔积的替代连接方法。

您可以将新查询视为这样编写:

SELECT
(
    SELECT
        COUNT ( t_01.rsecondary_objectu ) AS selectExpr
    FROM
        PIMANRELATION t_01
        INNER JOIN PITEMREVISION t_03 ON t_01.rprimary_objectu = t_03.puid
        CROSS JOIN PITEM t_02    
    WHERE
    t_01.rrelation_typeu = 'w8INy241VJFL2B'
    AND t_01.rsecondary_objectu = '2yLJkWqiVJFL2B'
) 
+ 
(  
    SELECT
        COUNT ( t_01.rsecondary_objectu ) AS selectExpr
    FROM
        PIMANRELATION t_01
        INNER JOIN PITEM t_02 ON t_01.rprimary_objectu = t_02.puid
        CROSS JOIN PITEMREVISION t_03
    WHERE
    t_01.rrelation_typeu = 'w8INy241VJFL2B'
    AND t_01.rsecondary_objectu = '2yLJkWqiVJFL2B'
    AND LNNVL(t_01.rprimary_objectu = t_03.puid)
)
from dual;
Run Code Online (Sandbox Code Playgroud)

现在查询的两个部分都有一个相等条件,因此 Oracle 可以使用索引为两者执行有效的嵌套循环连接。它仍然需要对查询的两个部分进行交叉联接,但与在同一查询中进行两次交叉联接相比,中间结果集的大小显着减少。例如,如果PIMANRELATION有1个相关行和PITEMREVISIONPITEM均具有一百万行,然后,如果你越过它们连接在一起你一万亿行。但是,如果您将查询拆分,那么您最终只会得到一百万行。

SQL Server 查询优化器有一个规则可以转换ORUNION ALL: SelToIdxStrategy。没有这方面的文档,我能找到的唯一参考就是这个答案。但是,该规则将不适用于这种情况。相反,您会得到两个只能通过嵌套循环连接实现的交叉连接。对于 中的每个相关行PIMANRELATION,SQL Server 将交叉联接到 中的所有行PITEM,然后交叉联接到 中的所有行PITEMREVISION,最后将过滤掉之后的行。您很容易最终会过滤掉数万亿行。

我有一个坏消息要告诉你。如果您确实无法更改查询文本的任何部分,并且您需要该查询执行良好,那么 SQL Server 可能不是适合您的应用程序的平台。数据库有不同的优点和缺点,您可能需要更改查询以适应这些差异。


ype*_*eᵀᴹ 9

这是仅基于查询结构的建议:

开始对执行计划、表统计和直方图、基数估计、隔离级别、内存设置和许多其他可能的问题原因和解决方法进行追查之前,请考虑它可能是应用程序中产生询问。

我的推理很简单:如果它看起来像垃圾并且是由 ORM 生成的,那么它很可能是垃圾。

我建议你检查:

  • 如果查询在原始 Oracle 数据库中运行完全相同,或者当目标是 SQL Server 数据库时,生成它的地方(以及 ORM/应用程序)会稍微或更多地更改它。

  • 有哪些测试表明查询与要应用的业务逻辑/要求一致。应用程序是否有这样的测试并且它们是否在(Oracle 和 SQL Server)环境中都成功通过?

我的观点是,在确定查询没有正确性问题之前,追逐性能问题是没有意义的。


详细地说,该查询几乎没有意义。该OR条件-这是二级(唯一的连接过滤器PITEMPITEMREVISION)表-本质上引入了一个交叉连接(这幸运的是,你已经踢在甲骨文的一些优化功能,但在SQL Server中见没有做。乔Obbish的答案了发生的事情的详细解释)。
为了更清楚,请考虑以下与您的查询等效的查询:

WITH
  t_01 AS
  ( SELECT rprimary_objectu
    FROM PIMANRELATION 
    WHERE rrelation_typeu = 'w8INy241VJFL2B'
      AND rsecondary_objectu = '2yLJkWqiVJFL2B'
  ),
  count_items AS
  ( SELECT COUNT(*) AS a
    FROM t_01
         JOIN PITEM t_02
         ON t_01.rprimary_objectu = t_02.puid
  ),
  count_revisions AS 
  ( SELECT COUNT(*) AS b
    FROM t_01
         JOIN PITEMREVISION t_03
         ON t_01.rprimary_objectu = t_03.puid
  ),
  count_all_items AS
  ( SELECT COUNT(*) AS aa
    FROM PITEM
  ),
  count_all_revisions AS
  ( SELECT COUNT(*) AS bb
    FROM PITEMREVISION
  )
SELECT
    (a * bb) + (b * aa) - (a * b) AS selectExpr
FROM
    count_items, count_all_items,
    count_revisions, count_all_revisions ;
Run Code Online (Sandbox Code Playgroud)

你明白为什么上述内容毫无意义吗?请注意count_all_itemscount_all_revisions计算如何计算这两个表中的所有行。我看不到这背后的业务逻辑。

(以上当然会更有效,因为它不做任何交叉连接,而是将表扫描分开并将计算转移到主查询中的简单乘法。无论优化器变得多么聪明,总是有限制他们可以提供的可能的转换和优化。)

可能使用这种奇怪计数的唯一情况是,是否在查询之后立即检查计数是否为0>= 1- 某些 ORM 似乎比更有效的EXISTS方法更喜欢的东西。在这种情况下,计数是 5 还是 500 万并不重要,因为目标是查找两个表中的任何一个中是否存在相关行。

  • 但是计数有什么用呢?我真的很好奇。我想不出任何用例。除非它只是检查计数是“0”还是“>= 1”。 (2认同)