简体   繁体   English

交叉应用(选择前1个)比row_number()慢得多

[英]Cross apply (select top 1) much slower than row_number()

Using AdventureWorks, listed below are queries for For each Product get any 1 row of its associated SalesOrderDetail . 下面列出了使用AdventureWorks的查询: For each Product get any 1 row of its associated SalesOrderDetail

Using cross apply it takes 14000ms. 使用cross apply需要14000毫秒。 The equivalent row_number version takes only 70ms (200x faster). 等效的row_number版本仅花费70毫秒(快200倍)。

cross apply is also slower than a simple inner join of all Products and SalesOrderDetails which returns 121317 rows (vs 266 rows when limited with TOP 1). cross apply也比所有产品和SalesOrderDetails的简单inner join联接要慢,后者返回121317行(与TOP 1限制时为266行)。

I prefer the cross apply syntax for this kind of query because it's cleaner than the row_number version. 我更喜欢这种查询的cross apply语法,因为它比row_number版本更干净。 But obviously the cross apply version is using a very inefficient execution plan and too slow to be usable. 但是很明显, cross apply版本使用的效率很低,执行计划太慢而无法使用。

It seems to me the query is not working as intended. 在我看来,查询未按预期工作。 It should not take 14 seconds to run this simple query. 运行此简单查询无需花费14秒。 I've used cross apply in other cases and never encountered anything this slow. 我在其他情况下使用了cross apply ,但从未遇到过如此缓慢的情况。 My question is: what about this particular query that is confusing the query optimizer? 我的问题是:这个使查询优化器感到困惑的特定查询怎么办? Is there any query hints that can be applied to help it use the optimal execution plan? 是否有任何查询提示可用于帮助其使用最佳执行计划? As suggested by @pacreely I've added statistics for each query. 正如@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;

And maybe related to this problem, cross apply without SalesOrderDetail.LineTotal is 10x faster. 可能与此问题相关,在不使用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;

在此处输入图片说明 Execution plans 执行计划

Cross apply statistics 交叉应用统计

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 statistics: 行号统计:

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.

Run your query with 使用运行查询

SET STATISTICS IO ON

You'll see that CROSS APPLY probably generates more Reads. 您会看到,CROSS APPLY可能会产生更多读取。 This is because you are doing multiple/duplicate reads of the Sales.SalesOrderDetails table 这是因为您正在对Sales.SalesOrderDetails表进行多次/重复读取

Also, don't assume the RowNumber query is "faster". 另外,不要假设RowNumber查询是“更快”的。 SQL has decided that it is an expensive query so it has gone parallel and used multiple processors "Fast but expensive on resources". SQL认为这是一个昂贵的查询,因此它已并行执行,并使用了多个处理器“速度快但资源昂贵”。 Run your query with 使用运行查询

SET STATISTICS TIME ON

Look at CPU time instead of elapsed time, this will give you the true speed of the query. 查看CPU时间而不是经过的时间,这将为您提供真正的查询速度。

When you look at the execution plan for each query examine the details of the Select component. 查看每个查询的执行计划时,请检查Select组件的详细信息。 There is a total cost measure for the query. 该查询有一个总成本度量。 If the cost is greater than the server's Max Degree of Parallelism (default is 5 unless your DBA changes it) then sql will produce a parallel query plan to improve Elapsed time. 如果开销大于服务器的最大并行度(除非您的DBA对其进行更改,否则默认值为5),那么sql将生成并行查询计划以缩短经过时间。

Try to get for the execution plan of the four statements together as one batch and see if the reported "percentage relative to the batch" for each one resembles your timings. 尝试将四个语句的执行计划作为一个批处理一起获得,并查看每个报告的“相对于该批处理的百分比”是否与您的时间安排相似。 It may be that you have some outdated statistics. 可能是您有一些过时的统计信息。

Thanks for all of your suggestions. 感谢您的所有建议。 As suggested by pacreely I checked and found out that LineTotal was actually a computed column. 如pacreely所建议,我检查并发现LineTotal实际上是一个计算列。 So it make sense it would slow everything down as the calculation is repeated for each row. 因此,在每一行重复进行计算时,它将减慢所有操作的速度。 However as shown above even without LineTotal, it's still too slow. 但是,如上所示,即使没有LineTotal,它仍然太慢。 Which then lead me to remove all columns but the Id from the cross apply clause. 然后,这导致我从cross apply子句中删除除Id外的所有列。 Finally I added an inner join to retrieve all the needed column. 最后,我添加了一个内部联接以检索所有需要的列。 This version of the cross apply query as fast as the row_number query 此版本的cross apply查询与row_number查询一样快

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
                                S.SalesOrderDetailID
                      FROM      Sales.SalesOrderDetail S
                      WHERE     S.ProductID = P.ProductID ) SODID
        INNER JOIN Sales.SalesOrderDetail SOD ON SOD.SalesOrderDetailID = SODID.SalesOrderDetailID

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM