交叉应用(选择前1)比row_number()

时间:2017-02-25 04:30:12

标签: sql sql-server performance tsql

使用AdventureWorks,下面列出的是For each Product get any 1 row of its associated SalesOrderDetail的查询。

使用cross apply需要14000毫秒。等效的row_number版本只需要70毫秒(快200倍)。

cross apply也比所有Products和SalesOrderDetails的简单inner join慢,返回121317行(当受TOP 1限制时为266行)。

我更喜欢这种查询的cross apply语法,因为它比row_number版本更清晰。但显然cross apply版本使用效率非常低的执行计划而且速度太慢而无法使用。

在我看来,查询无法按预期工作。运行这个简单的查询不应该花费14秒。我在其他情况下使用了cross apply,从未遇到过这么慢的事情。我的问题是:这个使查询优化器混淆的特定查询怎么样?是否有任何查询提示可用于帮助它使用最佳执行计划?正如@pacreely所建议的,我已经为每个查询添加了统计信息。

--CROSS APPLY ~14000ms
SELECT  P.ProductID
       ,P.Name
       ,P.ProductNumber
       ,P.Color
       ,SOD.SalesOrderID
       ,SOD.UnitPrice
       ,SOD.UnitPriceDiscount
       ,SOD.LineTotal
FROM    Production.Product P
        CROSS APPLY ( SELECT TOP 1
                                *
                      FROM      Sales.SalesOrderDetail S
                      WHERE     S.ProductID = P.ProductID ) SOD;

--ROW_NUMBER ~70ms
SELECT  *
FROM    ( SELECT    P.ProductID
                   ,P.Name
                   ,P.ProductNumber
                   ,P.Color
                   ,SOD.SalesOrderID
                   ,SOD.UnitPrice
                   ,SOD.UnitPriceDiscount
                   ,SOD.LineTotal
                   ,ROW_NUMBER() OVER ( PARTITION BY P.ProductID ORDER BY P.ProductID ) RowNum
          FROM      Production.Product P
                    INNER JOIN Sales.SalesOrderDetail SOD ON SOD.ProductID = P.ProductID ) X
WHERE   X.RowNum = 1;

--Simple INNER JOIN ~400ms (121317 rows)
SELECT  P.ProductID
       ,P.Name
       ,P.ProductNumber
       ,P.Color
       ,SOD.SalesOrderID
       ,SOD.UnitPrice
       ,SOD.UnitPriceDiscount
       ,SOD.LineTotal
FROM    Production.Product P
        INNER JOIN Sales.SalesOrderDetail SOD ON SOD.ProductID = P.ProductID;

也许与此问题有关,没有SalesOrderDetail.LineTotal的cross apply快10倍。

--CROSS APPLY (Without LineTotal) ~1200ms 
SELECT  P.ProductID
       ,P.Name
       ,P.ProductNumber
       ,P.Color
       ,SOD.SalesOrderID
       ,SOD.SalesOrderDetailID
       ,SOD.CarrierTrackingNumber
       ,SOD.OrderQty
       ,SOD.ProductID
       ,SOD.SpecialOfferID
       ,SOD.UnitPrice
       ,SOD.UnitPriceDiscount
       ,SOD.rowguid
       ,SOD.ModifiedDate
FROM    Production.Product P
        CROSS APPLY ( SELECT TOP 1
                                *
                      FROM      Sales.SalesOrderDetail S
                      WHERE     S.ProductID = P.ProductID ) SOD;

enter image description here Execution plans

交叉应用统计信息

SQL Server parse and compile time: 
   CPU time = 0 ms, elapsed time = 0 ms.

 SQL Server Execution Times:
   CPU time = 0 ms,  elapsed time = 0 ms.
SQL Server parse and compile time: 
   CPU time = 0 ms, elapsed time = 0 ms.

 SQL Server Execution Times:
   CPU time = 0 ms,  elapsed time = 0 ms.

 SQL Server Execution Times:
   CPU time = 0 ms,  elapsed time = 0 ms.

(266 row(s) affected)
Table 'SalesOrderDetail'. Scan count 1, logical reads 363114, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Product'. Scan count 1, logical reads 15, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

(1 row(s) affected)

 SQL Server Execution Times:
   CPU time = 15688 ms,  elapsed time = 16397 ms.
SQL Server parse and compile time: 
   CPU time = 0 ms, elapsed time = 0 ms.

 SQL Server Execution Times:
   CPU time = 0 ms,  elapsed time = 0 ms.

Row_number统计信息:

SQL Server parse and compile time: 
   CPU time = 0 ms, elapsed time = 0 ms.

 SQL Server Execution Times:
   CPU time = 0 ms,  elapsed time = 0 ms.
SQL Server parse and compile time: 
   CPU time = 0 ms, elapsed time = 0 ms.

 SQL Server Execution Times:
   CPU time = 0 ms,  elapsed time = 0 ms.

 SQL Server Execution Times:
   CPU time = 0 ms,  elapsed time = 0 ms.

(266 row(s) affected)
Table 'Product'. Scan count 9, logical reads 40, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'SalesOrderDetail'. Scan count 9, logical reads 1371, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Workfile'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

(1 row(s) affected)

 SQL Server Execution Times:
   CPU time = 360 ms,  elapsed time = 266 ms.
SQL Server parse and compile time: 
   CPU time = 0 ms, elapsed time = 0 ms.

 SQL Server Execution Times:
   CPU time = 0 ms,  elapsed time = 0 ms.

3 个答案:

答案 0 :(得分:2)

使用

运行查询
SET STATISTICS IO ON

您会看到CROSS APPLY可能会生成更多的Read。 这是因为您正在对Sales.SalesOrderDetails表进行多次/重复读取

此外,不要假设RowNumber查询“更快”。 SQL已经确定它是一个昂贵的查询,所以它已经并行并使用多个处理器“快速但资源昂贵”。 使用

运行查询
SET STATISTICS TIME ON

查看CPU时间而不是经过的时间,这将为您提供查询的真实速度。

当您查看每个查询的执行计划时,请检查Select组件的详细信息。查询有一个总成本度量。如果成本大于服务器的最大并行度(默认为5,除非您的DBA更改它),那么sql将生成并行查询计划以改善经过时间。

答案 1 :(得分:1)

尝试将四个语句的执行计划一起作为一个批处理,并查看报告的"相对于批处理的百分比"对于每一个都类似于你的时间。可能是您有一些过时的统计数据。

答案 2 :(得分:1)

感谢您的所有建议。正如pacreely所建议我检查并发现LineTotal实际上是一个计算列。所以有意义的是,当每行重复计算时,它会减慢一切。但是如上所示,即使没有LineTotal,它仍然太慢。然后引导我从bool operator ==(const node &X, constnode &Y) { return (X.X == X.Y && Y.X == Y.Y); } 子句中删除除Id之外的所有列。最后,我添加了一个内连接来检索所有需要的列。此版本的cross apply查询与cross apply查询

一样快
row_number