我昨天和今天花了几个小时试图解决为什么一个特定的数据库查询需要很长时间(3 分钟以上)才能在我在本地实例上恢复的数据库上执行,但在生产环境中只需要几分之一秒服务器。(这不仅仅是生产服务器具有更高规格的问题,因为相同的查询之前在我的本地实例上执行得很好)。
我将尝试以最好的方式解释这个问题——这意味着这篇文章可能有点长。查询非常简单:
SELECT [t0].[BillId], [t0].[CustomerId], [t0].[BillDate], [t0].[DueDate],
[t0].[Period], [t0].[Total], [t0].[Balance], [t0].[DateCreated], [t0].[DateModified] --, other fields
FROM [dbo].[Bills] AS [t0]
WHERE ([t0].[Period] = 201307) AND (([t0].[DateModified] >= '2013-07-04 17:00:00.000')
OR (([t0].[DateModified] < '2013-07-04 17:00:00.000') AND (EXISTS(
SELECT NULL AS [EMPTY]
FROM [dbo].[BillDetails] AS [t1]
WHERE [t1].[BillID] = [t0].[BillID] AND [t1].[DateModified] >= '2013-07-04 17:00:00.000'
))))
上述查询返回与某个时期相关联并且自特定时间点以来已被修改的账单。如果账单的 DateModified 字段中的值晚于指定日期,或者相关子行 ( ) 中的 DateModified 字段中的值晚于指定日期,则认为账单已修改BillDetails
。我将使用“delta”这个词来引用上述查询返回的结果。
这有点奇怪,但在我的本地实例上,这个查询的执行似乎在没有增量时特别阻塞,即当自上次指定日期以来没有任何更改时Bills
。BillDetails
当有要返回的增量时,查询需要几分之一秒。在实时/生产数据库上,这两种情况的查询响应都非常快。
我以前遇到过这个问题,但我没有记录我是如何解决它的——很可能是因为我无法从我尝试的许多事情中弄清楚是什么解决了它。在那段时间里,生产数据库上发生了“阻塞”,而我的本地实例很好。我怀疑它与索引有关。
当它现在在我的本地实例上再次出现时,索引是主要嫌疑人。我运行了我的实用程序脚本,用于重组平均碎片在 5% 到 30% 之间的索引,并重建平均碎片超过 30% 的索引。这似乎并没有解决问题。从那以后我尝试了很多东西,但修复它的方法是删除并重新创建表上的非聚集索引Bills
。
我在表上有三个非聚集索引,Bills
定义如下(或多或少):
NCI_IDX1 ([CustomerId] ASC)
NCI_IDX2 ([CustomerId] ASC, [Period] ASC, [BillId] ASC)
NCI_IDX3_Inc_Various ([Period] ASC, [DateModified] ASC, [BillId] ASC, [CustomerId] ASC) INCLUDE ([BillDate], [Total], [Balance] /* more fields */)
根据我在尝试解决问题时的经验,我有几个问题困扰着我。
问:重建索引有什么作用?它是否相当于手动删除和重新创建索引
问:删除索引会导致与删除索引相关的统计信息。重建索引是否“刷新”或“重置”相关统计信息?
问:鉴于查询优化器在选择执行计划时对统计数据的依赖,统计数据是否会导致查询执行的感知阻塞?
问:根据 MSDN,索引的统计信息不能使用DROP STATISTICS
. 这是否意味着必须删除并重新创建索引才能重置统计信息?
注意: LINQ 和 C# 被标记,因为上述查询的起源是在数据层中使用 LINQtoSQL 的 C# 应用程序。