SQL:存储过程需要 20/25 秒才能完成 运行,而完全相同的 select 在查询 window 中几乎是即时的 运行

SQL: Stored procedure takes 20/25 seconds to run while the exact same select is almost instant running in a query window

我已经阅读了 SP taking 15 minutes, but the same query when executed returns results in 1-2 minutes,但是 none 那里发布的建议似乎对我有用。 我有下一个 SQL SP:

ALTER PROCEDURE [dbo].[BL_GET_OW_AND_CATALOGUES_BY_SITE_FOR_ACTUAL_POSITION] 
       @PFK_ENTERPRISE int,
       @FK_SITE int,
       @PK_USER int
WITH RECOMPILE
AS
SET ARITHABORT ON;

DECLARE @PFK_ENTERPRISE_2 int = @PFK_ENTERPRISE
DECLARE @FK_SITE_2 int = @FK_SITE
DECLARE @PK_USER_2 int = @PK_USER

SELECT * INTO #markets_catalogues_tmp1 FROM markets_catalogues WHERE pfk_enterprise = @PFK_ENTERPRISE_2 and is_Active = 1 and FK_CATALOGUE_SETUP > 0
 
       SELECT    ow.PK_ORDER_WINDOW, ow.OW_DESCRIPTION
       FROM       ORDERS_WINDOW ow
             inner join ORDERS_WINDOW_CATALOGUES owc on ow.PFK_ENTERPRISE = owc.PFK_ENTERPRISE
                    and ow.PK_ORDER_WINDOW = owc.PFK_ORDER_WINDOW
             inner join CATALOGUES c on owc.PFK_ENTERPRISE = c.PFK_ENTERPRISE
                    and owc.PFK_CATALOGUE = c.PK_CATALOGUE
             inner join #markets_catalogues_tmp1 mc on 
                    owc.PFK_ENTERPRISE = mc.PFK_ENTERPRISE and owc.PFK_CATALOGUE = mc.PFK_CATALOGUE
             inner join MARKET m on 
                    m.PFK_ENTERPRISE = mc.PFK_ENTERPRISE and m.PK_MARKET = mc.PFK_MARKET 
             inner join USER_ACCESS_MARKETS uam on
                    m.PFK_ENTERPRISE = uam.PFK_ENTERPRISE AND m.PK_MARKET = uam.PFK_MARKET 
             inner join USERS u ON 
                    uam.PFK_ENTERPRISE = u.PFK_ENTERPRISE AND uam.PFK_USER = u.PK_USER 

       WHERE     (ow.PFK_ENTERPRISE = @PFK_ENTERPRISE_2) AND (ow.FK_ORDER_WINDOW_STATUS IN (1,2,3,5,6))
                           AND (IS_MAIN_CATALOG_RELATED = 1 OR FK_CATALOG_RELATED = 0)
                           AND (uam.PFK_USER = @PK_USER_2 OR @PK_USER_2 IS NULL)
                           AND c.FK_SITE = @FK_SITE_2
                           AND (c.IS_HISTORIC <> 1)
       group by ow.PK_ORDER_WINDOW, ow.OW_DESCRIPTION
       ORDER BY ow.OW_DESCRIPTION ASC

而且执行需要 20 多秒,但奇怪的是,如果我只是 运行 在新查询 window 中完全相同 select 结果几乎即时.

(原来存储的有点长,有3个select,但是一个就可以重现问题了)

我试过直接用tmp变量代替参数,我也试过

WITH RECOMPILE AS SET ARITHABORT ON;

无济于事,起初尝试 CTRL-L 检查显示瓶颈的执行计划我已经修复了,但现在我不能再尝试执行计划了,因为我正在将 select 存储到一个#tmp 变量。

我需要找出为什么这个查询用 exec 执行 SP 比 运行 单独 select 本身花费的时间长得多,换句话说,优化这个查询。

编辑 1:

如果我在单独的 window 中启动下一个查询,它会立即运行,并且 returns 与 运行 从 SP:

得到的结果完全相同
SELECT * INTO #markets_catalogues_tmp1 FROM markets_catalogues WHERE pfk_enterprise = 41 and is_Active = 1 and FK_CATALOGUE_SETUP > 0

       SELECT    ow.PK_ORDER_WINDOW, ow.OW_DESCRIPTION
       FROM       ORDERS_WINDOW ow
             inner join ORDERS_WINDOW_CATALOGUES owc on ow.PFK_ENTERPRISE = owc.PFK_ENTERPRISE
                    and ow.PK_ORDER_WINDOW = owc.PFK_ORDER_WINDOW
             inner join CATALOGUES c on owc.PFK_ENTERPRISE = c.PFK_ENTERPRISE
                    and owc.PFK_CATALOGUE = c.PK_CATALOGUE
             inner join #markets_catalogues_tmp1 mc on 
                    owc.PFK_ENTERPRISE = mc.PFK_ENTERPRISE and owc.PFK_CATALOGUE = mc.PFK_CATALOGUE
             inner join MARKET m on 
                    m.PFK_ENTERPRISE = mc.PFK_ENTERPRISE and m.PK_MARKET = mc.PFK_MARKET 
             inner join USER_ACCESS_MARKETS uam on
                    m.PFK_ENTERPRISE = uam.PFK_ENTERPRISE AND m.PK_MARKET = uam.PFK_MARKET 
             inner join USERS u ON 
                    uam.PFK_ENTERPRISE = u.PFK_ENTERPRISE AND uam.PFK_USER = u.PK_USER 

       WHERE     (ow.PFK_ENTERPRISE = 41) AND (ow.FK_ORDER_WINDOW_STATUS IN (1,2,3,5,6))
                           --and MARKETS_CATALOGUES.IS_ACTIVE = 1 
                           --and MARKETS_CATALOGUES.FK_CATALOGUE_SETUP > 0
                           AND (IS_MAIN_CATALOG_RELATED = 1 OR FK_CATALOG_RELATED = 0)
                           AND (uam.PFK_USER = 14118 OR 14118 IS NULL)
                           AND c.FK_SITE = 1
                           AND (c.IS_HISTORIC <> 1)
       group by ow.PK_ORDER_WINDOW, ow.OW_DESCRIPTION
       ORDER BY ow.OW_DESCRIPTION ASC

编辑 2:

我已经将 where 子句中的所有元素移动到连接中的相应 ON - 正如@CarlosSR 所建议的那样 - 所以查询现在看起来像这样:

   SELECT    ow.PK_ORDER_WINDOW, ow.OW_DESCRIPTION
   FROM       ORDERS_WINDOW ow
         inner join ORDERS_WINDOW_CATALOGUES owc on ow.PFK_ENTERPRISE = owc.PFK_ENTERPRISE 
                and ow.PFK_ENTERPRISE = @PFK_ENTERPRISE_2 
                and ow.FK_ORDER_WINDOW_STATUS IN (1,2,3,5,6)
                and ow.PK_ORDER_WINDOW = owc.PFK_ORDER_WINDOW

         inner join CATALOGUES c on owc.PFK_ENTERPRISE = c.PFK_ENTERPRISE
                and owc.PFK_CATALOGUE = c.PK_CATALOGUE
                and c.FK_SITE = @FK_SITE_2
                and (c.IS_MAIN_CATALOG_RELATED = 1 or c.FK_CATALOG_RELATED = 0)
                and c.IS_HISTORIC <> 1

         inner join #markets_catalogues_tmp1 mc on 
                owc.PFK_ENTERPRISE = mc.PFK_ENTERPRISE and owc.PFK_CATALOGUE = mc.PFK_CATALOGUE

         inner join MARKET m on 
                m.PFK_ENTERPRISE = mc.PFK_ENTERPRISE and m.PK_MARKET = mc.PFK_MARKET 

         inner join USER_ACCESS_MARKETS uam on
                m.PFK_ENTERPRISE = uam.PFK_ENTERPRISE and m.PK_MARKET = uam.PFK_MARKET 
                and (uam.PFK_USER = @PK_USER_2 OR @PK_USER_2 IS NULL)

         inner join USERS u ON 
                uam.PFK_ENTERPRISE = u.PFK_ENTERPRISE AND uam.PFK_USER = u.PK_USER 

   group by ow.PK_ORDER_WINDOW, ow.OW_DESCRIPTION
   ORDER BY ow.OW_DESCRIPTION ASC

但结果完全一样,20 秒 运行 查询。

编辑 3: 回答@Andrew Sayer

执行计划 - 起初 - 显示与内部 select

的连接存在瓶颈
inner join (SELECT * FROM markets_catalogues.....

(查询看起来像下一个)。

   SELECT    ow.PK_ORDER_WINDOW, ow.OW_DESCRIPTION
   FROM       ORDERS_WINDOW ow
         inner join ORDERS_WINDOW_CATALOGUES owc on ow.PFK_ENTERPRISE = owc.PFK_ENTERPRISE
                and ow.PK_ORDER_WINDOW = owc.PFK_ORDER_WINDOW
         inner join CATALOGUES c on owc.PFK_ENTERPRISE = c.PFK_ENTERPRISE
                and owc.PFK_CATALOGUE = c.PK_CATALOGUE
         inner join (SELECT * FROM markets_catalogues WHERE pfk_enterprise = @PFK_ENTERPRISE_2 and is_Active = 1 and FK_CATALOGUE_SETUP > 0) mc on 
                owc.PFK_ENTERPRISE = mc.PFK_ENTERPRISE and owc.PFK_CATALOGUE = mc.PFK_CATALOGUE
         inner join MARKET m on 
                m.PFK_ENTERPRISE = mc.PFK_ENTERPRISE and m.PK_MARKET = mc.PFK_MARKET 
         inner join USER_ACCESS_MARKETS uam on
                m.PFK_ENTERPRISE = uam.PFK_ENTERPRISE AND m.PK_MARKET = uam.PFK_MARKET 
         inner join USERS u ON 
                uam.PFK_ENTERPRISE = u.PFK_ENTERPRISE AND uam.PFK_USER = u.PK_USER 

   WHERE     (ow.PFK_ENTERPRISE = @PFK_ENTERPRISE_2) AND (ow.FK_ORDER_WINDOW_STATUS IN (1,2,3,5,6))
                       AND (IS_MAIN_CATALOG_RELATED = 1 OR FK_CATALOG_RELATED = 0)
                       AND (uam.PFK_USER = @PK_USER_2 OR @PK_USER_2 IS NULL)
                       AND c.FK_SITE = @FK_SITE_2
                       AND (c.IS_HISTORIC <> 1)
   group by ow.PK_ORDER_WINDOW, ow.OW_DESCRIPTION
   ORDER BY ow.OW_DESCRIPTION ASC

我所做的是将连接中的 SELECT 移动到一个单独的 #tmp 变量,实际上现在它的 运行ning 更快(因为同样的 select 是在一个 select 中使用,为简单起见我省略了)。起初三个selects的整个查询是1:20分钟,现在完整的查询是40秒,但与运行宁[=]时的即时结果相比仍然很慢81=]s 在查询中 window.

编辑4:估计的执行计划。

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

编辑5:实际执行计划。

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

编辑 6: 实际执行计划建议我为 #tmp table 创建一个索引。我可以这样创建索引:

SELECT * INTO #markets_catalogues_tmp1 FROM markets_catalogues WHERE pfk_enterprise = @PFK_ENTERPRISE_2 and is_Active = 1 and FK_CATALOGUE_SETUP > 0

CREATE NONCLUSTERED INDEX markets_catalogues_tmp1 ON [dbo].[#markets_catalogues_tmp1] ([pfk_enterprise]) INCLUDE ([is_active], [FK_CATALOGUE_SETUP])

但由于我对此不是很有经验,我不确定索引应该包含在 SP 本身还是外部,因为如果我把它放在里面,每次启动 SP 时都会创建索引?但是outside就不行了,因为是tmptable,所以有点懵

感谢更新。

您可以随时尝试索引建议 - 基准测试是否有帮助应该非常简单。我的直接想法是,两者都无济于事,但练习如何以可控的方式进行基准测试将是有益的。阅读这个问题,我们可以知道您已经尝试了很多不同的事情,并且不太清楚哪些更改有助于程序的行为更像独立程序,哪些更改改进了存储过程和独立程序,哪些没有区别但无论如何保留。

您还没有提供您希望其表现的实际执行计划,但让我们看看我们可以改进多少。

您的时间主要用于 USER_ACCESS_MARKETS 索引查找的嵌套循环。这需要很长时间,因为它运行了 539K 次,而它认为它只会执行 55 次。这个错误的估计可能是从 ORDERS_WINDOW_CATALOGUESCATALOGUES 之间的连接开始的,它认为这将导致 3 行但实际上是 returns 119 行(这种差异随着连接的增加而滚雪球)。这个连接有很多过滤器在起作用,两个在主要连接条件中看起来是聚集索引(应该没问题),然后是 c.IS_HISTORIC <> 1c.FK_SITE = @FK_SITE_2 过滤器,后者两个可能是低基数的原因。我的猜测是 c.FK_SITE = @FK_SITE_2 在这里是最重要的,我建议你看看这个连接在你的独立版本中是什么样的 - 其中嗅探 FK_SITE_2 变量会有所不同。

要解决这个基数问题,您可以为 CATALOGUES 创建一个临时 table,就像您已经对 markets_catalogues 所做的一样。如果有一种有效的方法可以找到与这些其他过滤器匹配的行,那么这将起作用。这将使 SQL 服务器有机会在填充此温度 table 后获得估计 - 更正它对 FK_SITE_2 过滤器的估计。

SELECT * INTO #CATALOGUES_tmp FROM CATALOGUES WHERE FK_SITE = @FK_SITE_2 and IS_HISTORIC <> 1

而不是解决基数估计(这里看起来有很多问题来源,您将需要解决很多问题),您可以通过使用临时 table 所以它必须被散列连接:

SELECT * INTO #USER_ACCESS_MARKETS_tmp FROM USER_ACCESS_MARKETS WHERE PFK_ENTERPRISE = @PFK_ENTERPRISE_2 and (PFK_USER = @PK_USER_2 OR @PK_USER_2 IS NULL)

或提示连接选项 - 告诉 SQL 服务器它应该散列连接到 USER_ACCESS_MARKETS

inner hash join USER_ACCESS_MARKETS uam on
       m.PFK_ENTERPRISE = uam.PFK_ENTERPRISE AND m.PK_MARKET = uam.PFK_MARKET 

那些临时 tables 可能会导致其他基数错误估计开始更多地影响计划,因此您需要对每次修改的实际执行计划进行评估 - 查看现在的时间,请参阅估计的来源。

进一步的优化可能来自注意到它花费了大约一秒钟的时间将 540K 行聚合回 119 - 这表明将连接更改为 semi-joins 或强制更早地进行区分以减少不必要的结果的数量通过是可能的。如果您可以这样做,以便仅在一小部分时间内完成与 USER_ACCESS_MARKETS 的缓慢嵌套循环连接,那么这也会带来巨大的好处(并且仍然可以是嵌套循环)。

我已经对语句的一部分进行了子查询,并在加入 USER_ACCESS_MARKETS 之前放置了一个 distinct,很难预测这会产生多大的影响,因为我不知道关于 market_catalogues 中的 PFK_MARKET 列的任何信息(此列是您从此连接中唯一关心的附加列)但有可能

ALTER PROCEDURE [dbo].[BL_GET_OW_AND_CATALOGUES_BY_SITE_FOR_ACTUAL_POSITION] 
       @PFK_ENTERPRISE int,
       @FK_SITE int,
       @PK_USER int
WITH RECOMPILE
AS
SET ARITHABORT ON;

DECLARE @PFK_ENTERPRISE_2 int = @PFK_ENTERPRISE
DECLARE @FK_SITE_2 int = @FK_SITE
DECLARE @PK_USER_2 int = @PK_USER

 
SELECT    sq.PK_ORDER_WINDOW, sq.OW_DESCRIPTION
FROM (
  SELECT DISTINCT ow.PK_ORDER_WINDOW, ow.OW_DESCRIPTION, m.PFK_ENTERPRISE, m.PK_MARKET
  FROM       ORDERS_WINDOW ow
        inner join ORDERS_WINDOW_CATALOGUES owc on ow.PFK_ENTERPRISE = owc.PFK_ENTERPRISE
               and ow.PK_ORDER_WINDOW = owc.PFK_ORDER_WINDOW
        inner join CATALOGUES c on owc.PFK_ENTERPRISE = c.PFK_ENTERPRISE
               and owc.PFK_CATALOGUE = c.PK_CATALOGUE
        inner join (SELECT * FROM markets_catalogues WHERE pfk_enterprise = @PFK_ENTERPRISE_2 and is_Active = 1 and FK_CATALOGUE_SETUP > 0) mc on 
               owc.PFK_ENTERPRISE = mc.PFK_ENTERPRISE and owc.PFK_CATALOGUE = mc.PFK_CATALOGUE
        inner join MARKET m on 
               m.PFK_ENTERPRISE = mc.PFK_ENTERPRISE and m.PK_MARKET = mc.PFK_MARKET 
  WHERE     (ow.PFK_ENTERPRISE = @PFK_ENTERPRISE_2) AND (ow.FK_ORDER_WINDOW_STATUS IN (1,2,3,5,6))
                      AND (c.IS_MAIN_CATALOG_RELATED = 1 OR c.FK_CATALOG_RELATED = 0)
                      AND c.FK_SITE = @FK_SITE_2
                      AND (c.IS_HISTORIC <> 1)
  ) sq     
        inner join USER_ACCESS_MARKETS uam on
               sq.PFK_ENTERPRISE = uam.PFK_ENTERPRISE AND sq.PK_MARKET = uam.PFK_MARKET 
           AND (uam.PFK_USER = @PK_USER_2 OR @PK_USER_2 IS NULL)
        inner join USERS u ON 
               uam.PFK_ENTERPRISE = u.PFK_ENTERPRISE AND uam.PFK_USER = u.PK_USER   
  group by sq.PK_ORDER_WINDOW, sq.OW_DESCRIPTION
  ORDER BY sq.OW_DESCRIPTION ASC

我还回到了 markets_catalogues 没有临时 table 的查询版本,你应该先尝试将数据放入临时 table .

我想知道这些计划是否与您通过独立声明获得的计划相似。