在CTE中使用COUNT()比在CTE之外使用更昂贵?

时间:2014-10-16 09:49:58

标签: sql database performance tsql sql-server-2012

我正在使用SQL Server进行分页,我希望通过计算结果总数作为部分结果集的一部分来避免重复,而不是获取结果集,然后执行单独的查询以获得计数。然而,麻烦的是,它似乎增加了执行时间。例如,如果我查看SET STATISTICS TIME ON,请执行以下操作:

WITH PagedResults AS (
    SELECT
        ROW_NUMBER() OVER (ORDER BY AggregateId ASC) AS RowNumber,
        COUNT(PK_MatrixItemId) OVER() AS TotalRowCount,
        *
    FROM [MyTable] myTbl WITH(NOLOCK)
)
SELECT * FROM PagedResults
WHERE RowNumber BETWEEN 3 AND 4810

......或者这个(执行计划相同):

SELECT * FROM (
    SELECT TOP (4813)
        ROW_NUMBER() OVER (ORDER BY AggregateId ASC) AS RowNumber,
        COUNT(PK_MatrixItemId) OVER() AS TotalRowCount,
        *
    FROM [MyTable] myTbl WITH(NOLOCK)
) PagedResults
WHERE PagedResults.RowNumber BETWEEN 3 AND 4810

...似乎平均CPU时间(所有查询加起来)是此时间的1.5到2倍:

SELECT * FROM (
    SELECT TOP (4813)
        ROW_NUMBER() OVER (ORDER BY AggregateId ASC) AS RowNumber,
        *
    FROM [MyTable] myTbl WITH(NOLOCK)
) PagedResults
WHERE PagedResults.RowNumber BETWEEN 3 AND 4810

SELECT COUNT(*) FROM [MyTable] myTbl WITH(NOLOCK)

显然我宁愿使用前者而不是后者,因为后者冗余地重复FROM子句(并且如果我有的话会重复任何WHERE子句),但它的执行时间是如此之多我真的必须使用它。有没有办法可以让前者的执行时间缩短?

1 个答案:

答案 0 :(得分:1)

CTE被内联到查询计划中。它们的执行与派生表完全相同。

派生表与物理操作不对应。它们不会将结果集“具体化”到临时表中。 (我相信MySQL会这样做,但MySQL是最原始的主流RDBMS。)

使用OVER()确实在查询计划中表现为缓冲到临时表。完全不清楚为什么在这里比仅仅重新阅读基础表更快。缓冲相当慢,因为写入比SQL Server中的读取更加CPU密集。我们可以从原始表中读取两次。这可能是后一种选择更快的原因。

如果要避免重复查询的某些部分,请使用视图或表值函数。当然,这些对于一次性查询来说不是很好的选择。您还可以在应用程序层中生成SQL并重用字符串。 ORM也使这更容易。