【问题标题】:TOP slows down queryTOP 减慢查询速度
【发布时间】:2011-10-04 15:23:17
【问题描述】:

我对包含数百万行的表进行了数据透视查询。正常运行查询,运行2秒,返回2983行。如果我将 TOP 1000 添加到查询中,则需要 10 秒才能运行。

这可能是什么原因造成的?

SELECT * 
  FROM (SELECT l.PatientID,
               l.LabID,
               l.Result
          FROM dbo.Labs l
          JOIN (SELECT MAX(LabDate) maxDate, 
                       PatientID, 
                       LabID 
                  FROM dbo.Labs 
              GROUP BY PatientID, LabID) s ON l.PatientID = s.PatientID
                                          AND l.LabID = s.LabID
                                          AND l.LabDate = s.maxDate) A
 PIVOT(MIN(A.Result) FOR A.LabID IN ([1],[2],[3],[4],[5],[7],[8],[9],[10],[11],[12],[13],[14],[15],[16],[17])) p

执行计划:

这个替代公式有同样的问题:

select
    * 
FROM (
    SELECT 
        l.PatientID,
        l.LabID,
        l.Result
    FROM dbo.Labs l
    where l.LabDate = (
        select 
            MAX(LabDate) 
        from Labs l2 
        where l2.PatientID = l.PatientID 
            and l2.LabID = l.LabID
    )
) A
PIVOT(MIN(A.Result) FOR A.LabID IN ([1],[2],[3],[4],[5],[7],[8],[9],[10],[11],[12],[13],[14],[15],[16],[17])) p

【问题讨论】:

  • 您查看过每个查询版本的查询计划吗?
  • 你在哪里添加TOP 1000
  • TOP 版本有两个聚集索引扫描,普通版本有一个。我不知道这意味着什么。
  • @Quassnoi 在第一个 SELECT @OMG Ponies 2008R2 之后
  • 您将TOP 放在查询中的什么位置?

标签: sql sql-server tsql sql-server-2008 pivot


【解决方案1】:
SELECT  TOP 1000
        *
FROM    (
        SELECT  patientId, labId, result,
                DENSE_RANK() OVER (PARTITION BY patientId, labId ORDER BY labDate DESC) dr
        FROM    labs
        ) q
PIVOT   (
        MIN(result)
        FOR
        labId IN ([1],[2],[3],[4],[5],[7],[8],[9],[10],[11],[12],[13],[14],[15],[16],[17])
        ) p
WHERE   dr = 1
ORDER BY
        patientId

您也可以尝试像这样创建索引视图:

CREATE VIEW
        v_labs_patient_lab
WITH SCHEMABINDING
AS
SELECT  patientId, labId, COUNT_BIG(*) AS cnt
FROM    dbo.labs
GROUP BY
        patientId, labId

CREATE UNIQUE CLUSTERED INDEX
        ux_labs_patient_lab
ON      v_labs_patient_lab (patientId, labId)

并在查询中使用它:

SELECT  TOP 1000
        *
FROM    (
        SELECT  lr.patientId, lr.labId, lr.result
        FROM    v_labs_patient_lab vl
        CROSS APPLY
                (
                SELECT TOP 1 WITH TIES
                       result
                FROM   labs l
                WHERE  l.patientId = vl.patientId
                       AND l.labId = vl.labId
                ORDER BY
                       l.labDate DESC
                ) lr
        ) q
PIVOT   (
        MIN(result)
        FOR
        labId IN ([1],[2],[3],[4],[5],[7],[8],[9],[10],[11],[12],[13],[14],[15],[16],[17])
        ) p
ORDER BY
        patientId

【讨论】:

  • 那不检索相同的结果。 LabDate 似乎将记录分开。 i.imgur.com/8xlmn.png
  • 这行得通。无论有没有 TOP,它都会在 4 秒内运行。我仍然很好奇为什么 TOP 会减慢原始查询的速度
  • @τεκ:对于使用TOP+ORDER BYJOINPIVOT 组合的查询,SQL Server 可能并不总是擅长找出最佳查询计划。
  • 有没有办法提出更好的查询计划?如果可行的话,我更喜欢我的原始方法来对函数进行排名。
  • @τεκ:您的第一个计划使用segment / top(您可以看到计划中根本没有加入)。这需要扫描整个表。第二个计划包含实际的连接,但由于patientId, labId, labDate (很可能)是PRIMARY KEY 的主要部分(我假设这是因为我在流聚合和合并连接之前看不到任何排序)这更具可扩展性,因为它具有零响应时间,因此与TOP 中的记录数成线性关系。
【解决方案2】:

查询的处理有一个特定的顺序。

一个普通的SQL查询会这样写:

SELECT [...]
  FROM [table1]
  JOIN [table2]
    ON [condition]
 WHERE [...]
 GROUP BY [...]
HAVING [...]
 ORDER BY [...]

但处理顺序不同:

FROM [table1]
    ON [condition]
  JOIN [table2]
 WHERE [...]
 GROUP BY [...]
HAVING [...]
SELECT [...]
 ORDER BY [...]

当使用SELECT DISTINCT [...]SELECT TOP [...]时,处理顺序如下:

FROM [table1]
    ON [condition]
  JOIN [table2]
 WHERE [...]
 GROUP BY [...]
HAVING [...]
SELECT [...] DISTINCT[...]
ORDER BY [...]
TOP [....]

因此,您的 SELECT TOP 1000 最后处理时间会更长。

查看此链接了解更多详情: http://blogs.msdn.com/b/sqlqueryprocessing/

【讨论】:

  • 五倍?如果 TOP 1000 最后处理,那是不是意味着在检索所有结果后只取前 1000 个结果?我怀疑它会这样做。
  • @τεκ 要正确订购它们,它必须检索所有这些。
【解决方案3】:

在谷歌上搜索了一些关于执行计划的建议后,我找到了解决方案。

SELECT TOP 1000 * 
FROM (SELECT l.PatientID,
               l.LabID,
               l.Result
          FROM dbo.Labs l
          JOIN (SELECT MAX(LabDate) maxDate, 
                       PatientID, 
                       LabID 
                  FROM dbo.Labs 
              GROUP BY PatientID, LabID) s ON l.PatientID = s.PatientID
                                          AND l.LabID = s.LabID
                                          AND l.LabDate = s.maxDate) A
PIVOT(MIN(A.Result) FOR A.LabID IN ([1],[2],[3],[4],[5],[7],[8],[9],[10],[11],[12],[13],[14],[15],[16],[17])) p
OPTION (HASH JOIN)

OPTION (HASH JOIN) 就是这样。带有 TOP 的版本的最终执行计划看起来像原始的非顶级执行计划,最后添加了一个 TOP。

由于我最初是在一个视图中执行此操作,因此我最终所做的是将JOIN 更改为INNER HASH JOIN

【讨论】:

    猜你喜欢
    • 2015-11-27
    • 2017-12-24
    • 2016-08-28
    • 1970-01-01
    • 2021-10-09
    • 1970-01-01
    • 2019-11-22
    • 2011-02-19
    • 2020-04-24
    相关资源
    最近更新 更多