优化查询以减少逻辑读取

Mau*_*cio 1 performance sql-server sql-server-2016 query-performance

我有一个返回 127K 行的 SQL Server 2016 查询。您可以在此处找到查询和查询计划。如果您还需要表结构,请告诉我。

我需要加入一个只有 20 行的表,作为其中一个产品的替代品。换句话说,我从主表中查询产品,但在某些条件下,其中一些可以被其他产品替换。

问题是,对于那个简单的表,我有 254K 的逻辑读取。我试过LEFT JOINOUTER APPLY

关于如何替换它以避免大量逻辑读取的任何建议?只是提一下,只有 1 个产品有替代品。

Jos*_*ell 7

仔细查看执行计划 XML,请注意这些有问题的统计信息:

<Wait WaitType="ASYNC_NETWORK_IO" WaitTimeMs="1328" WaitCount="403"/> 

<QueryTimeStats CpuTime="353" ElapsedTime="1853"/>
Run Code Online (Sandbox Code Playgroud)

查询花费了 1.3 秒等待应用程序使用的结果。查询总共只运行了 1.8 秒。所以这里的主要问题是应用程序正在逐行使用这些 127k 结果。查询本身运行得相当快。

Forrest McDaniel 有一篇很好的博客文章展示了这个问题:两个简单的 ASYNC_NETWORK_IO 演示

答案的其余部分解决了问题的“逻辑读取”部分。


OUTER APPLY'd 表 ( DBVAREKT)上所有这些逻辑读取的原因在这里:

在计划资源管理器中搜索索引的屏幕截图

“索引查找”对连接的上输入上的每一行执行一次NESTED LOOPS。因此,该索引 ( ID1) 中有 127,329 次搜索,即使最后只返回了 302 个匹配行。

优化器通常不会选择在索引中进行那么多搜索,但它只认为上面的输入会有 82 行。做82次搜索肯定更合理。


解决此问题的一般方法是避免NESTED LOOPS在该特定表上进行连接,因为这是问题的根源。为此,您可以使用连接提示,但我很难说应该在哪里应用提示。

Randi提到可能重写查询,将一些数据放入临时表,本质上将查询分解为更小的块,SQL Server 优化器可以更好地处理这些块。您可以OUTER APPLY按如下方式分解:

SELECT 
       1756,
       L.MADTYPE,
       DBM.VNR1
INTO #results
FROM dbo.STDORDRE S
INNER JOIN dbo.STDORD STO ON sto.DATO = s.DATO
                           AND sto.KUNDE = s.KUNDE
LEFT JOIN dbo.STDORDML L ON L.ONR = s.ONR
CROSS APPLY (SELECT dbo.MCS_ClarionDateToSQL(D.DATO) SQL_DATO,
                    DATEPART(dw, dbo.MCS_ClarionDateToSQL(D.DATO)) DP,  D.VNR1, D.DATO, D.LINE, D.KATALOGNR, D.VFAKTOR, D.MADTYPE FROM dbo.DBMENU D WHERE D.SNR = s.VARENR AND D.LINE = L.MENULINE) DBM
INNER JOIN dbo.DBKUNDE kun ON kun.NR = s.KUNDE
INNER JOIN dbo.DBKUNGRP dbk ON dbk.NR = kun.GRP
INNER JOIN dbo.DBVARE varm ON varm.NR = s.VARENR
INNER JOIN dbo.DBVARE var ON var.NR = DBM.VNR1
LEFT OUTER JOIN dbo.MENORDRE MEN ON MEN.KUNDE = s.KUNDE
                                AND MEN.DATO  = DBM.DATO
                                AND MEN.LINIE = DBM.LINE
                                AND MEN.NR    = s.MNR
WHERE 1 = 1
  AND ( kun.UDSKREVET = 0
                     OR ( kun.UDSKREVET = 1
                          AND kun.UDSDATO >= 79627 ))
                   AND varm.TYPE = 9
                   AND varm.KPFIX = 0
                   AND s.ML = 1
                   AND DBM.DATO BETWEEN 79627 AND 79777
                   AND sto.TYPE = 1;

SELECT 
       1756,
       L.MADTYPE,
       DBM.VNR1
FROM #results RES  
OUTER APPLY (SELECT MTY.PREC MTY_PREC, 
                    MTY.NR   MTY_NR ,
                    VAR1.PREC VAR1_PREC, 
                    var1.VAR_PKG_ID VAR1_VAR_PKG_ID,
                    VAR1.NR VAR1_NR ,
                    VAR1.TYPE VAR1_TYPE,
                    VAR1.GRP VAR1_GRP
             FROM dbo.DBVAREKT VKT
              LEFT JOIN dbo.DBVARE VAR1 ON VAR1.PREC = VKT.TO_VARE_PREC
                                               AND (ISNULL(VKT.MADTYPE,0) <> 0  )
              LEFT JOIN dbo.DBMTYPE MTY ON MTY.PREC = VKT.TO_MADTYPE_PREC
            WHERE 1 = 1
              AND VKT.MADTYPE = RES.MADTYPE
              AND VKT.VARENR  = RES.VNR1
                      ) OA;
Run Code Online (Sandbox Code Playgroud)