视图上的 SELECT COUNT(*) 比同一视图上的 SELECT * 慢几个数量级

Nat*_*ate 10 performance view azure-sql-database query-performance

风景

CREATE VIEW [dbo].[vProductList]
WITH SCHEMABINDING
AS 

SELECT
     p.[Id]
    ,p.[Name]
    ,price.[Value] as CalculatedPrice
    ,orders.[Value] as OrdersWithThisProduct
FROM 
    products as p 
    INNER JOIN productMetadata as price ON p.Id = price.ProductId AND price.MetaId = 1
    INNER JOIN productMetadata as orders ON p.Id = orders.ProductId AND orders.MetaId = 2
Run Code Online (Sandbox Code Playgroud)

为简单起见,假设productMetadataProductId, MetaId, Value有~87m 行,products表中有大约 400k 行。

针对此视图的一般查询完美地工作:

SELECT * FROM vProductList WHERE CalculatedPrice > 500
Run Code Online (Sandbox Code Playgroud)

查询结果在 2-4 秒内(通过 vpn 和远程,所以我很擅长)。

将上述更改为计数同样快:

SELECT COUNT(*) from vProductList WHERE CalculatedPrice > 500
Run Code Online (Sandbox Code Playgroud)

与原始选择的运行时间大致相同,我再次同意。大约有 10,000 种产品符合此标准。

我遇到过两个不同的情况,事情变得非常奇怪并且永远存在。

第一的

对视图中基表中的列之一进行查询:

SELECT * FROM vProductList WHERE Name = 'Hammer' 
Run Code Online (Sandbox Code Playgroud)

这个查询需要一点时间运行(20-30 秒)并返回 ~30k 结果;但是,对上述查询略有改动:

SELECT COUNT(*) FROM vProductList WHERE Name = 'Hammer' 
Run Code Online (Sandbox Code Playgroud)

需要 13 分钟才能返回一个表示 ~30k 的计数。

第二

做一个WHERE IN子查询

SELECT * FROM vProductList WHERE Id IN (SELECT ProductId FROM TableThatHasFKToProductId and ColumnInTable = 'Yes')
Run Code Online (Sandbox Code Playgroud)

此查询返回约 300k 行并需要两分钟才能返回(我相信大部分时间只是将数据下载到 SSMS 中);但是,将其更改为SELECT COUNT(*)需要 20 分钟的查询结果。

SELECT COUNT(*) FROM vProductList WHERE Id IN (SELECT ProductId FROM TableThatHasFKToProductId and ColumnInTable = 'Yes')
Run Code Online (Sandbox Code Playgroud)

为什么SELECT *比 快SELECT COUNT

我将 SSMS 提供的总执行时间用于此处列出的所有时间。

执行计划

计划 SELECT 1 FROM v WHERE IN (...)

计划 SELECT COUNT(0) FROM v WHERE IN (...)

注意:我尝试使用 PasteThePlan,但它一直告诉我的计划无效 xml。

Pau*_*ite 5

从提供的执行计划中,对于COUNT优化器围绕最终连接选择本地/全局聚合策略的情况:

最终加入

不幸的是,优化器高估了本地聚合的有效性。它估计有 136 行驱动嵌套循环连接,而在运行时遇到 366,115 行。

估计

对于本地 SQL Server 实例,366,115 次索引查找可能不是什么大问题,但计划中包含的等待统计数据显示了当前 Azure SQL 数据库配置的 I/O(可能还有内存)限制:

等待统计

该计划SELECT 1显示了一个专门的散列和合并连接策略,在这种情况下使用非常有限的内存和/或 I/O 功能产生更好的结果。

您可能会看到带有OPTION (HASH JOIN, MERGE JOIN)提示的第一个查询的性能更好,但基本问题是由大量连接驱动的基数/数据分布估计不佳。

不要被针对每个计划运算符显示的成本百分比所误导 - 这些数字目前来自优化器对成本的估计(使用抽象模型)。这些数字不反映运行时间条件或成本。

估计行数和实际行数之间的大偏差通常会导致问题。对于导致优化器选择在特定硬件设置上不能很好扩展的策略的低估尤其如此。