返回固定行数后查询暂停

Jon*_*des 9 sql-server-2008

我有一个视图,它可以快速运行(几秒钟)最多 41 条记录(例如TOP 41),但需要几分钟才能运行 44 条或更多条记录,如果使用TOP 42或运行会产生中间结果TOP 43。具体来说,它将在几秒钟内返回前 39 条记录,然后在返回剩余记录之前暂停近三分钟。这个模式在查询TOP 44or时是一样的TOP 100

这个视图最初是从一个基本视图派生出来的,在基本视图中只添加了一个过滤器,下面代码中的最后一个。如果我从基础链接子视图,或者如果我使用内联基础中的代码编写子视图,似乎没有区别。基本视图在几秒钟内返回 100 条记录。我想我可以让子视图以与基础一样快的速度运行,而不是慢 50 倍。有没有人见过这种行为?关于原因或解决方案的任何猜测?

这种行为在过去几个小时内一直是一致的,因为我已经测试了所涉及的查询,尽管在事情开始变慢之前返回的行数略有上升和下降。这并不新鲜;我现在正在查看它,因为总运行时间是可以接受的(<2 分钟),但我已经在相关的日志文件中看到这种暂停至少几个月了。

阻塞

我从未见过查询被阻止,即使数据库上没有其他活动(由 sp_WhoIsActive 验证),问题仍然存在。基本视图包括NOLOCK整个内容,这是值得的。

查询

这是子视图的简化版本,为简单起见,基本视图内联。它仍然表现出运行时间的跳跃,大约有 40 条记录。

SELECT TOP 100 PERCENT
    Map.SalesforceAccountID AS Id,
    CAST(C.CustomerID AS NVARCHAR(255)) AS Name,
    CASE WHEN C.StreetAddress = 'Unknown' THEN '' ELSE C.StreetAddress                 END AS BillingStreet,
    CASE WHEN C.City          = 'Unknown' THEN '' ELSE SUBSTRING(C.City,        1, 40) END AS BillingCity,
                                                       SUBSTRING(C.Region,      1, 20)     AS BillingState,
    CASE WHEN C.PostalCode    = 'Unknown' THEN '' ELSE SUBSTRING(C.PostalCode,  1, 20) END AS BillingPostalCode,
    CASE WHEN C.Country       = 'Unknown' THEN '' ELSE SUBSTRING(C.Country,     1, 40) END AS BillingCountry,
    CASE WHEN C.PhoneNumber   = 'Unknown' THEN '' ELSE C.PhoneNumber                   END AS Phone,
    CASE WHEN C.FaxNumber     = 'Unknown' THEN '' ELSE C.FaxNumber                     END AS Fax,
    TransC.WebsiteAddress AS Website,
    C.AccessKey AS AccessKey__c,
    CASE WHEN dbo.ValidateEMail(C.EMailAddress) = 1 THEN C.EMailAddress END,  -- Removing this UDF does not speed things
    TransC.EmailSubscriber
    -- A couple dozen additional TransC fields
FROM
    WarehouseCustomers AS C WITH (NOLOCK)
    INNER JOIN TransactionalCustomers AS TransC WITH (NOLOCK) ON C.CustomerID = TransC.CustomerID
    LEFT JOIN  Salesforce.AccountsMap AS Map WITH (NOLOCK) ON C.CustomerID = Map.CustomerID
WHERE
        C.DateMadeObsolete IS NULL
    AND C.EmailAddress NOT LIKE '%@volusion.%'
    AND C.AccessKey IN ('C', 'R')
    AND C.CustomerID NOT IN (243566)  -- Exclude specific test records
    AND EXISTS (SELECT * FROM Orders AS O WHERE C.CustomerID = O.CustomerID AND O.OrderDate >= '2010-06-28')  -- Only count customers who've placed a recent order
    AND Map.SalesforceAccountID IS NULL  -- Only count customers not already uploaded to Salesforce
-- Removing the ORDER BY clause does not speed things up
ORDER BY
    C.CustomerID DESC
Run Code Online (Sandbox Code Playgroud)

Id IS NULL过滤器会丢弃由BaseView;返回的大部分记录。如果没有TOP子句,它们分别返回 1,100 条记录和 267K。

统计数据

运行时TOP 40

SQL Server parse and compile time:    CPU time = 234 ms, elapsed time = 247 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.

(40 row(s) affected)
Table 'CustomersHistory'. Scan count 2, logical reads 39112, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Orders'. Scan count 1, logical reads 752, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'AccountsMap'. Scan count 1, logical reads 458, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

SQL Server Execution Times:   CPU time = 2199 ms,  elapsed time = 7644 ms.
Run Code Online (Sandbox Code Playgroud)

运行时TOP 45

(45 row(s) affected)
Table 'CustomersHistory'. Scan count 2, logical reads 98268, physical reads 1, read-ahead reads 3, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Orders'. Scan count 1, logical reads 1788, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'AccountsMap'. Scan count 1, logical reads 2152, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

SQL Server Execution Times: CPU time = 41980 ms,  elapsed time = 177231 ms.
Run Code Online (Sandbox Code Playgroud)

我很惊讶地看到实际输出的这种适度差异导致读取次数增加了 ~3 倍。

比较执行计划,除了返回的行数外,它们是相同的。与上面的统计数据一样,TOP 45查询中早期步骤的实际行数要高得多,而不仅仅是高 12.5%。

概括地说,它是从 Orders 中扫描一个覆盖索引,从 WarehouseCustomers 中寻找相应的记录;将此循环加入到 TransactionalCustomers(远程查询,确切计划未知);并将其与 AccountsMap 的表扫描合并。远程查询是估计成本的 94%。

杂记

早些时候,当我将视图的扩展内容作为独立查询执行时,它运行得非常快:100 条记录需要 13 秒。我现在正在测试一个精简版的查询,没有子查询,这个简单得多的查询需要三分钟才能要求返回超过 40 行,即使作为独立查询运行也是如此。

子视图包含大量读取(每个 sp_WhoIsActive 约 1M),但在这台机器上(8 个内核,32 GB RAM,95% 专用 SQL 盒),这通常不是问题。

我已经多次删除并重新创建了这两个视图,没有任何变化。

数据不包括任何 TEXT 或 BLOB 字段。一个字段涉及 UDF;删除它不会阻止暂停。

无论是在服务器上查询还是在 1,400 英里外的工作站上查询,时间都是相似的,因此延迟似乎是查询本身固有的,而不是将结果发送到客户端。

JNK*_*JNK 4

一些值得尝试的事情:

  1. 检查你的索引

    • 所有JOIN关键字段都已索引吗?如果您经常使用此视图,我什至会为视图中的条件添加过滤索引。例如...

    • CREATE INDEX ix_CustomerId ON WarehouseCustomers(CustomerId, EmailAddress) WHERE DateMadeObsolete IS NULL AND AccessKey IN ('C', 'R') AND CustomerID NOT IN (243566)

  2. 更新统计数据

    • 过时的统计数据可能存在问题。如果你能摆动它,我会做一个FULLSCAN。如果存在大量行,则数据可能已发生显着更改而不会触发自动重新计算。
  3. 清理查询

    • 制作Map JOINa NOT EXISTS- 您不需要该表中的任何数据,因为您只需要不匹配的记录

    • 去除ORDER BY。我知道评论说这并不重要,但我发现这很难相信。对于较小的结果集来说,这可能并不重要,因为数据页已经被缓存。