6

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

设置如下:

  • Windows Server 2008(非 R2)64 位,8 GB RAM
  • SQL Server 速成版 2008 R2
  • 数据库大小为 6 GB(mdf 文件大小)
  • 查询主要从中选择的表(Orders)有大约 24000 条记录,其他五个连接表很小(100 条记录或更少)
  • 该表Orders有一varbinary(MAX)Report包含二进制数据(PDF 文档),平均大小约为 200 到 300 kB(但有时可能高达 2 MB)。在这 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 可以在0和之间4。这些查询从来都不是问题。

  • 以及两个变体 (*)

    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 应用程序)。(默认情况下,实体框架等待结果的时间似乎不会超过 30 秒。)
  • 如果我将查询粘贴到 SSMS 中并让查询运行(等待 60 秒),则结果将成功返回,并且下一个相同的查询会在不到一秒的时间内再次运行。
  • 大约三周后,同样的情况再次发生(但查询运行的时间将是 65 或 70 秒)

一个额外的观察:

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

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

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

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

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

编辑

按照下面评论中的 bhamby 提示,我SET STATISTICS TIME ON在 SSMS 中设置,然后再次运行查询。当查询再次变慢时,我得到一个很高的值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 秒。

4

2 回答 2

2

这似乎很浪费

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 行,那么您的查询时间会超过几秒钟,这是一件愚蠢的事情。

于 2013-07-10T19:09:37.290 回答
0

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

在 Entity Framework 中,您应该能够将该过程作为Function Import 导入

然后,您可以通过提供查询提示或打击Parameter Sniffing来控制存储过程的执行计划。

听起来你的服务器的执行计划每 3 周就会过时一次,因此速度变慢了。

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

于 2013-08-07T06:52:27.133 回答