CPUが100%、実行プランが不良


8

特定のクエリで使用されている実行プランに問題があるため、CPUスパイクが100%になるという大きな問題があります。私は今、自分で解決するのに数週間を費やしています。

私のデータベース

私のサンプルDBには、3つの簡略化されたテーブルが含まれています。

[データ・ロガー]

CREATE TABLE [model].[DataLogger](
    [ID] [bigint] IDENTITY(1,1) NOT NULL,
    [ProjectID] [bigint] NULL,
CONSTRAINT [PK_DataLogger] PRIMARY KEY CLUSTERED 
(
    [ID] ASC
)WITH (STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]

【インバーター】

CREATE TABLE [model].[Inverter](
    [ID] [bigint] IDENTITY(1,1) NOT NULL,
    [SerialNumber] [nvarchar](50) NOT NULL,
 CONSTRAINT [PK_Inverter] PRIMARY KEY CLUSTERED 
(
    [ID] ASC
)WITH (STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF) ON [PRIMARY],
 CONSTRAINT [UK_Inverter] UNIQUE NONCLUSTERED 
(
    [DataLoggerID] ASC,
    [SerialNumber] ASC
)WITH (STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]

ALTER TABLE [model].[Inverter] WITH CHECK
ADD CONSTRAINT [FK_Inverter_DataLogger]
FOREIGN KEY([DataLoggerID])
REFERENCES [model].[DataLogger] ([ID])

[InverterData]

CREATE TABLE [data].[InverterData](
    [InverterID] [bigint] NOT NULL,
    [Timestamp] [datetime] NOT NULL,
    [DayYield] [decimal](18, 2) NULL,
 CONSTRAINT [PK_InverterData] PRIMARY KEY CLUSTERED 
(
    [InverterID] ASC,
    [Timestamp] ASC
)WITH (STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF)
)

統計とメンテナンス

[InverterData]表には、毎月のジャンクに分割(複数のインスタンスのPaaSの点で異なる)複数の万行が含まれています。

すべてのインデクサーは最適化され、すべての統計情報は、必要に応じて毎日/毎週の順番で再構築/再編成されます。

私のクエリ

クエリはEntity Frameworkで生成され、また単純です。しかし、私は毎分1,000回実行し、パフォーマンスは不可欠です。

SELECT
[Extent1].[InverterID] AS [InverterID],
[Extent1].[DayYield] AS [DayYield]
FROM [data].[InverterDayData] AS [Extent1]
INNER JOIN [model].[Inverter] AS [Extent2] ON [Extent1].[InverterID] = [Extent2].[ID]
INNER JOIN [model].[DataLogger] AS [Extent3] ON [Extent2].[DataLoggerID] = [Extent3].[ID]
WHERE ([Extent3].[ProjectID] = @p__linq__0)
AND ([Extent1].[Date] = @p__linq__1) OPTION (MAXDOP 1)

MAXDOP 1ヒントは遅いparalel計画に別の問題のためです。

「良い」計画

時間の90%以上で、使用済みプランは非常に高速で、次のようになります。

迅速な計画

問題

その日のうちに、良い計画はランダムに悪い遅い計画に変わりました。

「悪い」計画は10〜60分間使用され、その後「良い」計画に変更されます。「悪い」計画では、CPUが最大100%まで急上昇します。

これはどのように見えるかです:

スロープラン

これまでにやってみたこと

私が最初に思ったのHash Matchは、悪い子だということでした。そこで、新しいヒントを使用してクエリを変更しました。

...Extent1].[Date] = @p__linq__1) OPTION (MAXDOP 1, LOOP JOIN)

LOOP JOIN使用することを強制する必要があるNested Loopの瞬間をHash Match

その結果、90%の計画は以前のようになります。しかし、計画はまた、ランダムに悪い計画に変更されました。

「悪い」計画は次のようになります(テーブルループの順序が変更されました)。

また遅い計画

「新しい悪い」計画の間、CPUは100%までピークします。

解決?

「良い」計画を強制することが私の心に浮かびます。しかし、これが良い考えかどうかはわかりません。

計画内には、すべての列を含む推奨索引があります。しかし、これによりテーブル全体が2倍になり、頻繁に発生する挿入が遅くなります。

私を助けてください!


更新1-@Jamesコメントに関連

以下は両方のプランです(実際のテーブルからのものであるため、いくつかの追加フィールドがプランに表示されています)。

いい計画

悪い計画1(ハッシュマッチ)

悪い計画2(入れ子ループ)

アップデート2-@David Fowler answereに関連

悪い計画は、ランダムなパラメーター値を実行しています。だから私@p__linq__1 ='2016-11-26 00:00:00.0000000' @p__linq__0 =20825は通常、ホールデイと同じ価値で来る悪い計画よりも。

ストアドプロシージャからのパラメータスニッフィングの問題と、SP内でそれらを回避する方法を知っています。私のクエリでこの問題を回避する方法についてのヒントはありますか?

推奨インデックスを作成すると、すべての列が含まれます。これにより、テーブル全体が2倍になり、頻繁に発生する挿入が遅くなります。これは、テーブルを複製するだけのインデックスを作成する「感覚」ではありません。また、この大きなテーブルのデータサイズを2倍にすることを意味します。

更新3-@David Fowlerコメントに関連

それも機能しなかったし、できなかったと思います。理解を深めるために、クエリの呼び出し方法について説明します。

[DataLogger]テーブルに3つのエンティティがあるとします。1日の間に、同じ3つのクエリを何度も往復で呼び出します。

基本クエリ: ...WHERE ([Extent3].[ProjectID] = @p__linq__0) AND ([Extent1].[Date] = @p__linq__1)

パラメータ:

  1. @p__linq__0 = 1; @p__linq__1 = '2018-01-05 00:00:00.0000000'
  2. @p__linq__0 = 2; @p__linq__1 = '2018-01-05 00:00:00.0000000'
  3. @p__linq__0 = 3; @p__linq__1 = '2018-01-05 00:00:00.0000000'

パラメータ@p__linq__1は常に同じ日付です。しかし、以前は適切なプランで数千回実行されるクエリで、ランダムに悪いプランを選択します。同じパラメーターで!

更新4-@Nicコメントに関連

メンテナンスは毎晩実行され、次のようになります。

インデックス

インデックスが5%以上断片化されている場合、再編成されます...

ALTER INDEX [{index}] ON [{table}] REORGANIZE

インデックスがさらに断片化されている場合、30%が再構築されます...

ALTER INDEX [{index}] ON [{table}] REBUILD WITH (ONLINE=ON, MAXDOP=1)

インデックスがパーティション化されている場合、フラグメント化について証明され、パーティションごとに変更されます...

ALTER INDEX [{index}] ON [{table}] REBUILD PARTITION = {partitionNr} WITH (ONLINE=ON, MAXDOP=1)

統計

modification_counter0より大きい場合、すべての統計が更新されます...

UPDATE STATISTICS [{schema}].[{object}] ([{stats}]) WITH FULLSCAN

またはパーティションに分割されています。

UPDATE STATISTICS [{schema}].[{object}] ([{stats}]) WITH RESAMPLE ON PARTITIONS({partitionNr})

メンテナンスには、すべての統計情報と、自動生成された統計情報が含まれます。

例


これについては言及していませんが、最近これらのテーブルの統計を更新しましたか?
Nic

Thx @Nic質問の更新を追加しました。詳細はこちらをご覧ください。
Steffen Mangold 2018年

回答:


3

計画を見て、良いものと悪いものの間にはいくつかの違いがあります。最初に気づくのは、良いプランがInverterDayDataでシークを実行し、両方の悪いプランがスキャンを実行することです。これはなぜですか?推定された行を確認すると、適切な計画は1行を期待しているのに対し、悪い計画は6661行と約7000行を期待していることがわかります。

コンパイルされたパラメータ値を見てみましょう。

良い計画 @ p__linq__1 = '2016-11-26 00:00:00.0000000' @ p__linq__0 = 20825

悪い計画 @ p__linq__1 = '2018-01-03 00:00:00.0000000' @ p__linq__0 = 20686

それで、それはパラメータースニッフィング問題であるように私に見えます、それが悪いパフォーマンスをしているとき、あなたはそのクエリにどんなパラメーター値を渡しますか?

賢明に見えるInverterDayDataの悪い計画にはインデックスの推奨事項があります。それを実行して、役立つかどうかを確認してみます。SQLがテーブルに対してスキャンを実行できるようにする場合があります。


回答ありがとうございます!(最後に)あなたのために質問を変更しました。
Steffen Mangold 2018年

提案されたインデックスがテーブル全体をカバーしていることに気づかなかった、それは良い考えではありません。日付列のインデックスを試すことができます。
デビッドファウラー

OPTIMIZE FORヒントを使用すると、プランは強制的に適切な値でコンパイルされますOPTION(OPTIMIZE FOR(@ p__linq__1 = '2016-11-26 00:00:00.0000000'、@ p__linq__0 20825))が注意してテストしてください予想外の頭痛を引き起こさないようにするためです
David Fowler

ちょっと@david更新を投稿しました。
Steffen Mangold 2018年

OK、パラメータスニッフィングの問題でした。パーティションテーブル内の行数が異なるため、インデックスSEEKまたはSCANが選択されます。...OPTION (OPTIMIZE FOR UNKNOWN)ヒントで修正しました。
Steffen Mangold 2018年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.