SQL Server 查询突然变慢

Posted

技术标签:

【中文标题】SQL Server 查询突然变慢【英文标题】:SQL Server query suddenly slow 【发布时间】:2013-07-10 18:14:59 【问题描述】:

我遇到一个 SQL 数据库查询问题,突然(但通常大约每三周一次)变慢。

设置如下:

Windows Server 2008(非 R2)64 位,8 GB RAM SQL Server Express 2008 R2 数据库大小为 6 GB(mdf 文件大小) 查询主要从中选择的表 (Orders) 有大约 24000 条记录,其他五个连接表很小(100 条记录或更少) 表Orders 有一个varbinary(MAX)Report,其中包含平均大小约为200 到300 kB(但有时可达2 MB)的二进制数据(PDF 文档)。在这 24000 个订单中,超过 90% 的订单填写了此列,其他订单为 NULL,即 6 GB 数据库大小的 90% 以上是二进制数据。

相关查询具有以下结构:

SELECT TOP (30) [Project2].[OrderID] AS [OrderID]
                -- around 20 columns more
FROM ( SELECT [Project2].[OrderID] AS [OrderID],
              -- around 20 columns more
              row_number() OVER (ORDER BY [Project2].[OrderID] ASC) AS [row_number]
       FROM ( SELECT [Filter1].[OrderID] AS [OrderID]
              -- around 20 columns more
              FROM ( SELECT [Extent1].[OrderID] AS [OrderID]
                     -- around 20 columns more
                     FROM [dbo].[Orders] AS [Extent1]
                     INNER JOIN -- small table
                     LEFT OUTER JOIN  -- small table
                     LEFT OUTER JOIN  -- small table
                     LEFT OUTER JOIN  -- small table
                     LEFT OUTER JOIN  -- small table
                     WHERE ([Extent1].[Status] IS NOT NULL) 
                       AND (4 = CAST( [Extent1].[Status] AS int))
                       AND ([Extent1].[SomeDateTime] IS NULL)
                       AND ([Extent1].[Report] IS NULL)
                   ) AS [Filter1]
              OUTER APPLY  (SELECT TOP (1) [Project1].[C1] AS [C1]
                            FROM ( SELECT CAST( [Extent7].[CreationDateTime] AS datetime2) AS [C1],
                                                [Extent7].[CreationDateTime] AS [CreationDateTime]
                                   FROM [dbo].[OtherTable] AS [Extent7]
                                   WHERE [Filter1].[OrderID] = [Extent7].[OrderID]
                                 ) AS [Project1]
                             ORDER BY [Project1].[CreationDateTime] DESC
             ) AS [Limit1]
       )  AS [Project2]
)  AS [Project2]
WHERE [Project2].[row_number] > 0
ORDER BY [Project2].[OrderID] ASC

它是由实体框架从 LINQ-to-Entities 查询生成的。查询出现在几个变体中,仅在第一个 WHERE 子句中有所不同:

五种变体

WHERE ([Extent1].[Status] IS NOT NULL) 
  AND (X = CAST( [Extent1].[Status] AS int))

X 可以介于 04 之间。这些查询从来都不是问题。

还有两个变种(*)

WHERE ([Extent1].[Status] IS NOT NULL) 
  AND (4 = CAST( [Extent1].[Status] AS int))
  AND ([Extent1].[SomeDateTime] IS NULL)
  AND ([Extent1].[Report] IS NULL)

... IS NOT NULL... 在最后一行。我只有这两个查询才有下面描述的问题。

“现象”是:

这两个查询 (*) 每周运行 5 天,每天运行 100 到 200 次。他们用不到一秒钟的时间完成了大约三周的表演。 三周后,两个查询突然需要超过 60 秒。 (这个时间实际上随着数据库大小的增加而增加。)由于超时,用户会收到一个错误(在网页上;它是一个 Web 应用程序)。 (默认情况下,Entity Framework 等待结果的时间似乎不会超过 30 秒。) 如果我将查询粘贴到 SSMS 中并让查询运行(等待 60 秒),则结果会成功返回,并且下一个相同的查询会在不到一秒的时间内再次运行。 大约三周后,同样的情况再次发生(但查询运行的时间将是 65 或 70 秒)

补充观察:

如果我在查询正常的时候重启SQL Server服务进程,进程的内存使用会慢慢增加。它在大约一周内逐步达到大约 1.5 GB(任务管理器中的私有工作集)的限制。 如果我在查询突然变慢时重新启动 SQL Server 服务进程并再次触发查询,我可以在任务管理器中看到该服务在几秒钟内加载了将近 1 GB。

不知何故,我怀疑整个问题与 Express 版本的内存限制 (1 GB) 和 varbinary(MAX) 列有关,尽管我只是在检查列值是否为 @987654335 的 WHERE 子句中使用它@ 或不NULLReport 列本身不是选定的列之一。

由于我正在违反明年最新的 Express 版本的限制(10 GB mdf 文件大小),因此我正在考虑进行更改:

要么将二进制列移动到另一个表并通过 FILESTREAM 将内容存储在外部,继续使用 Express Edition 使用没有 Express 限制的“大”SQL Server 版本之一,将二进制列保留在 Orders 表中 两者兼得

问题:查询突然变慢的原因是什么?我计划的其中一项更改能否解决问题或是否有其他解决方案?

编辑

按照下面 cmets 中的 bhamby 提示,我在 SSMS 中设置了 SET STATISTICS TIME ON,然后再次运行查询。当查询再次变慢时,我得到SQL Server parse and compile time 的高值,即:CPU time = 27,3 secElapsed time = 81,9 sec。查询的执行时间仅为 CPU 时间 = 0.06 秒,经过时间 = 2.8 秒。之后第二次运行查询会为 SQL Server 解析和编译时间提供 0.06 秒的 CPU 时间和经过的时间 = 0.08。

【问题讨论】:

您能隔离与这 3 周间隔一致的任何进程吗?似乎很奇怪,除了竞争之外的任何事情都会导致这种不一致的表现。 @GoatCO:我已经检查了好几次,但与其他进程没有竞争。频率不是确切地 3 周,它可能或多或少几天,它可能发生在早上或下午。当它发生时,服务器上既没有高 CPU 也没有内存负载。并且问题永远不会自行消失(如果其他一些竞争过程结束,我会期望)。到目前为止,我发现的唯一方法是在 SSMS 中运行一次查询。 啊,这很有趣。如果您不时在 SSMS 中运行它,您可以完全防止减速,还是仍然会发生? 首先我会去规范化并将报告放在单独的表或外部。听起来像是在内存中而不是从磁盘中读取。我只有 6 GB 的数据,其中大部分是报告。如果您从查询中取出报告,那么 1 GB 的内存不会被滥用。 我想知道您看到的增加的时间是否是您的查询的执行计划必须重新编译的时间。下次遇到问题时,尝试在 SSMS 中的查询前添加SET STATISTICS TIME ON;,看看标记为SQL Server parse and compile time 的部分是否真的很高。我也很想知道在您重新启动后第一次执行查询是否需要很长时间......这对我来说表明这可能是一个问题。 【参考方案1】:

这看起来很浪费

SELECT TOP (1) [Project1].[C1] AS [C1]
FROM ( SELECT CAST( [Extent7].[CreationDateTime] AS datetime2) AS [C1],
                    [Extent7].[CreationDateTime] AS [CreationDateTime]
         FROM [dbo].[OtherTable] AS [Extent7]
        WHERE [Filter1].[OrderID] = [Extent7].[OrderID]
     ) AS [Project1]
ORDER BY [Project1].[CreationDateTime] DESC

SELECT max( CAST( [Extent7].[CreationDateTime] AS datetime2) ) AS [C1]
  FROM [dbo].[OtherTable] AS [Extent7]
 WHERE [Filter1].[OrderID] = [Extent7].[OrderID]

为什么不将日期存储为日期时间?

我不喜欢那个外挂 我会创建一个运行一次并加入它的#temp 确保并将 [OrderID] 声明为 PK

SELECT [Extent7].[OrderID], max( CAST( [Extent7].[CreationDateTime] AS datetime2) ) AS [C1]
FROM [dbo].[OtherTable] AS [Extent7]
GROUP BY [Extent7].[OrderID]

您可以进行循环连接

接下来我会把它放在#temp2 中,这样你就可以确定它只运行一次 再次确保将 OrderID 声明为 PK

SELECT [Extent1].[OrderID] AS [OrderID]
                     -- around 20 columns more
                     FROM [dbo].[Orders] AS [Extent1]
                     INNER JOIN -- small table
                     LEFT OUTER JOIN  -- small table
                     LEFT OUTER JOIN  -- small table
                     LEFT OUTER JOIN  -- small table
                     LEFT OUTER JOIN  -- small table
                     WHERE ([Extent1].[Status] IS NOT NULL) 
                       AND (4 = CAST( [Extent1].[Status] AS int))
                       AND ([Extent1].[SomeDateTime] IS NULL)
                       AND ([Extent1].[Report] IS NULL)

如果 Order 只有 24,000 行,那么您的查询时间会超过几秒钟,这很愚蠢。

【讨论】:

+1:使用max 是个好点子!我从另一个查询中获取了相应的 LINQ 查询,在该查询中,我实际上选择了比 CreationDateTime 更多的列。但在这个版本中,max 似乎确实更聪明。我会试试的!顺便说一句:CreationDateTime 存储为datetime2(0)。无论出于何种原因,(冗余)CAST 都是由实体框架生成的。 OrderID 是 PK 顺便说一句。 然后取出演员,因为这需要时间。【参考方案2】:

如果它是一个经常运行的查询,那么我建议将其转换为存储过程并使用该过程的结果。

在实体框架中,您应该能够将过程作为Function Import 导入。

然后您可以通过给它query hints 或打击Parameter Sniffing 来控制存储过程的执行计划。

感觉你的服务器的执行计划每 3 周就会过期一次,因此速度变慢了。

另外,您提到您使用的是 64 位 SQL。我的经验是 64 位 SQL 对子查询的执行效率并不高。我的建议是尽量避免它们。

【讨论】:

以上是关于SQL Server 查询突然变慢的主要内容,如果未能解决你的问题,请参考以下文章

如何避免从 C# 构建的 Sql Server 2005 参数化查询变慢

为啥 SQL Server 标量值函数变慢?

表单查询突然变慢原因查找及处理

sql server建立索引导致插入数据库变慢

当数据库变慢时的解决方法都有哪些

插入几行后,Sql server Insert 变慢