SQL データベース クエリが突然(ただし定期的には約 3 週間ごとに) 遅くなるという問題があります。
セットアップは次のとおりです。
- Windows Server 2008 (R2 以外) 64 ビット、8 GB RAM
- SQL Server Express 2008 R2
- データベースのサイズは 6 GB (mdf ファイル サイズ)
- クエリが主に選択しているテーブル (
Orders
) には約 24000 レコードがあり、他の 5 つの結合テーブルは小さい (100 レコード以下) - テーブル
Orders
には、平均サイズが約 200 ~ 300 kB のバイナリ データ (PDF ドキュメント) を含むvarbinary(MAX)
列があります (ただし、最大で 2 MB になる場合もあります)。Report
これらの 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
これは、Entity Framework による LINQ-to-Entities クエリから生成されます。WHERE
クエリは、最初の句のみが異なるいくつかのバリエーションで発生します。
5つのバリエーション
WHERE ([Extent1].[Status] IS NOT NULL) AND (X = CAST( [Extent1].[Status] AS int))
0
X は ~ の間で指定できます4
。これらのクエリは決して問題ではありません。と 2 つのバリアント (*)
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...
最後の行に。以下に説明する問題は、これら 2 つのクエリでのみ発生します。
「現象」は次のとおりです。
- 2 つのクエリ (*) は、1 日あたり 100 ~ 200 回、週 5 日実行されます。約 3 週間、1 秒未満で動作します。
- 3 週間後、両方のクエリが突然 60 秒以上必要になりました。(この時間は、データベースのサイズが大きくなるにつれて実際には長くなります。) タイムアウトにより、ユーザーは (Web ページ上で、つまり Web アプリで) エラーを受け取ります。(Entity Framework は、デフォルトで結果を 30 秒以上待機しないようです。)
- クエリを SSMS に貼り付けてクエリを実行すると (60 秒待機)、結果が正常に返され、次の同じクエリが 1 秒以内に再度実行されます。
- 約 3 週間後、同じことが再び起こります (ただし、クエリの実行時間は 65 秒または 70 秒になります)。
追加の観察:
- クエリが正常に実行されているときに SQL Server サービス プロセスを再起動すると、プロセスのメモリ使用量がゆっくりと増加します。約 1 週間以内に、段階的に約 1.5 GB (タスク マネージャーのプライベート ワーキング セット) の制限に達します。
- クエリが突然遅くなったときに SQL Server サービス プロセスを再起動し、クエリを再度トリガーすると、サービスが数秒以内にほぼ 1 GB をロードすることがタスク マネージャーで確認できます。
どういうわけか、問題全体が Express エディションと列のメモリ制限 (1 GB) に関係していると思われますが、列の値が であるかどうかをチェックする句で使用varbinary(MAX)
しているだけです。列自体は、選択された列の 1 つではありません。WHERE
NULL
NULL
Report
私は来年最新の Express エディションの制限 (10 GB の mdf ファイル サイズ) に対して実行しているので、とにかく変更を検討しています。
- バイナリ列を別のテーブルに移動し、FILESTREAM を介してコンテンツを外部に保存し、Express Edition を使用し続ける
- Express の制限なしで「大きな」SQL Server エディションのいずれかを使用し、バイナリ列を
Orders
テーブルに保持します - 両方を行う
質問: クエリが突然遅くなった理由は何ですか? 私が計画している変更の 1 つは問題を解決できますか、それとも他の解決策はありますか?
編集
以下のコメントにある bhamby のヒントに従ってSET STATISTICS TIME ON
、クエリを再度実行する前に SSMS で設定しました。クエリが再び遅くなるとSQL Server parse and compile time
、 、つまりCPU time = 27,3 sec
との値が高くなりElapsed time = 81,9 sec
ます。クエリの実行時間は、CPU 時間 = 0.06 秒、経過時間 = 2.8 秒のみです。その後クエリを 2 回実行すると、CPU 時間は 0.06 秒、経過時間は SQL Server の解析時間とコンパイル時間で 0.08 秒になります。