web-dev-qa-db-ja.com

SQL Server 2016の不正なクエリプランが週に1回DBをロックする

週に1回、過去5週間、ほぼ同じ時刻(早朝、ユーザーが使用を開始したときのユーザーアクティビティに基づく場合があります)、SQL Server 2016(AWS RDS、ミラーリング)は多くのタイムアウトを開始しますクエリ。

すべてのテーブルでUPDATE STATISTICSを実行すると、常にすぐに修正されます。

初回以降、すべてのテーブルのすべての統計を(毎週ではなく)毎晩更新しましたが、それでも発生しました(統計の更新が実行されてから約8時間後、毎日実行されるわけではありません)。

今回は、クエリストアを有効にして、どの特定のクエリ/クエリプランであるかを確認できるかどうかを確認しました。私はそれを1つに絞り込むことができたと思います:

Bad query plan

そのクエリを見つけた後、このあまり使用されないクエリから欠落している推奨インデックスを追加しました(ただし、これは頻繁に使用される多くのテーブルに影響します)。

不適切なクエリプランは、インデックススキャンを実行していました(1万行しかないテーブルで)。同じスキャンを実行するために使用された、ミリ秒単位で返された他のクエリプラン。最新のクエリプランは、新しいインデックスを作成した後、シークのみを行います。しかし、そのインデックスがなくても、99%の時間で数ミリ秒以内に戻ってきましたが、毎週、40秒以上かかります。

これは、2012年からSQL Server 2016に移行した後に起こりました。

DBCC CHECKDBはエラーを返しません。

  1. 新しいインデックスは問題を修正し、悪い計画を二度と選択しないようにしますか?
  2. 現在うまく機能している計画を「強制」する必要がありますか?
  3. これが別のクエリ/プランで発生しないことを確認するにはどうすればよいですか?
  4. これはより大きな問題の症状ですか?

追加したばかりのインデックス:

CREATE NONCLUSTERED INDEX idx_AppointmetnAttendee_AttendeeType
ON [dbo].[AppointmentAttendee] ([UserID],[AttendeeType])

CREATE NONCLUSTERED INDEX [idx_appointment_start] ON [dbo].[Appointment]
(
    [ProjectID] ASC,
    [Start] ASC
)
INCLUDE (   [ID],
    [AllDay],
    [End],
    [Location],
    [Notes],
    [Title],
    [CreatedByID]) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]

完全なクエリテキスト:

https://Pastebin.com/Z5szPBf (LINQで生成、選択した列を最適化できる/できるはずですが、この問題とは無関係です)

あなたの質問とは異なる順序で質問に答えます。

4。これはより大きな問題の症状ですか?

SQL Server 2016の new cardinality estimator が問題の原因である可能性があります。 SQL Server 2012はレガシーCEを使用しており、そのバージョンでは問題は発生していません。新しいカーディナリティエスティメータは、データについてさまざまな仮定を行い、同じSQLに対してさまざまなクエリプランを生成できます。クエリとデータによっては、レガシーCEを使用した一部のクエリのパフォーマンスが向上する場合があります。そのため、データモデルの一部が新しいCEに最適でない場合があります。それは大丈夫ですが、今のところ新しいCEを回避する必要があるかもしれません。

また、統計情報が毎日更新される場合でも、クエリのパフォーマンスに一貫性がないことも気になります。注意すべき重要な点の1つは、すべてのテーブルの統計を収集すると、キャッシュからすべてのクエリプランが効果的に消去されるため、統計に問題が発生したり、パラメータースニッフィングに関連する可能性があることです。データモデル、データ変更率、統計更新ポリシー、コードの呼び出し方法などに関する多くの情報なしに判断を下すのは困難です。SQLServer 2016はいくつかの データベースレベル設定を提供しています役立つパラメータsniffing の場合、問題のある1つのクエリだけでなく、アプリケーション全体に影響を与える可能性があります。

この動作につながる可能性のあるシナリオ例を破棄します。あなたが言った:

一部のユーザーは1つの許可レコードを持つことができ、一部のユーザーは最大20kです。

すべてのクエリプランを一掃するすべてのテーブルの統計を収集するとします。上記の要因に応じて、1日の最初のクエリが1つのアクセス許可レコードのみを持つユーザーに対するものである場合、SQL Serverは、1つのレコードを持つユーザーには適切に機能しますが、20kレコードを持つユーザーにはひどく機能するプランをキャッシュします。その日の最初のクエリが2万件のレコードを持つユーザーに対するものである場合は、2万件のレコードについて適切な計画が得られる可能性があります。 1レコードのユーザーに対してコードを実行すると、最適なクエリではない可能性がありますが、ミリ秒で終了する可能性があります。これは、実際にはパラメータースニッフィングのように聞こえます。問題が常に表示されない理由、または表示されるまでに数時間かかる場合がある理由を説明しています。

1。新しいインデックスは問題を修正し、悪い計画を二度と選択しないようにしますか?

特にスキャンが早期に終了できない場合は、追加したインデックスの1つを使用すると、インデックスを介して必要なデータにアクセスする方が、テーブルに対してクラスター化インデックススキャンを実行するよりも安価になるため、問題を防ぐことができると思います。クエリプランの悪い部分を拡大してみましょう。

bad query plan

SQL Serverでは、[Permission][Project]の結合から1行のみが返されると推定されています。外部入力の各行について、[Appointment]でクラスター化インデックススキャンを実行します。すべての行がこのテーブルからスキャンされますが、[Start]のフィルタリングに一致する行のみが結合演算子に返されます。結合演算子内では、結果はさらに減少します。

結合の外部入力に送信された行が1つだけの場合、上記のクエリプランは問題ありません。ただし、結合からのカーディナリティの見積もりが間違っていて、たとえば1000行が得られた場合、SQL Serverは[Appointment]で1000のクラスター化インデックススキャンを実行します。クエリプランのパフォーマンスは、見積もりの​​問題に非常に敏感です。

そのクエリプランを二度と取得しない最も直接的な方法は、[Appointment]テーブルに対してカバーするインデックスを作成することです。 [ProjectId][Start]のインデックスのようなものがそれを行うべきです。これは正確に、問題に対処するために作成した[idx_appointment_start]インデックスのようです。 SQLサーバーがクエリプランを選択しないようにするもう1つの方法は、[Permission][Project]の結合からカーディナリティの見積もりを修正することです。これを行う一般的な方法には、コードの変更、統計の更新、レガシーCEの使用、複数列の統計の作成、RECOMPILEヒントなどのローカル変数に関する詳細情報のSQL Serverへの提供、またはそれらの行の一時テーブル。これらの手法の多くは、msレベルの応答時間が必要な場合や、ORMを介してコードを記述する必要がある場合には適切なアプローチではありません。

[AppointmentAttendee]で作成したインデックスは、問題に直接対処する方法ではありません。ただし、インデックスで複数列の統計を取得するため、これらの統計は不適切なクエリプランを妨げる可能性があります。インデックスは、データにアクセスするためのより効率的な方法を提供する可能性がありますが、これも不適切なクエリプランを阻止する可能性がありますが、[AppointmentAttendee]のインデックスだけで再び発生しないという保証はありません。

3。これが別のクエリ/プランで発生しないことを確認するにはどうすればよいですか?

なぜこの質問をしているのか分かりますが、非常に広い質問です。私の唯一のアドバイスは、クエリプランが不安定になる根本的な原因をよりよく理解し、ワークロードに対して適切なインデックスが作成されていることを検証し、ワークロードを慎重にテストおよび監視することです。 Microsoftは、SQL Server 2016の新しいCEによって引き起こされるクエリプランの回帰に対処する方法に関するいくつかの 一般的なアドバイス を持っています。

クエリプロセッサを最新バージョンのコードにアップグレードするための推奨ワークフローは次のとおりです。

  1. データベースの互換性レベルを変更せずにデータベースをSQL Server 2016にアップグレードします(以前のレベルを維持します)

  2. データベースでクエリストアを有効にします。クエリストアの有効化と使用の詳細については、「クエリストアを使用したパフォーマンスの監視」を参照してください。

  3. ワークロードの代表的なデータを収集するのに十分な時間待ちます。

  4. データベースの互換性レベルを130に変更します

  5. SQL Server Management Studioを使用して、互換性レベルの変更後に特定のクエリでパフォーマンスの低下がないかどうかを評価します

  6. 回帰がある場合は、クエリストアに以前の計画を強制します。

  7. 強制に失敗するクエリプランがある場合、またはそれでもパフォーマンスが不十分な場合は、互換性レベルを以前の設定に戻してから、Microsoftカスタマーサポートに連絡することを検討してください。

SQL Server 2012にダウングレードして最初からやり直す必要があると言っているわけではありませんが、説明されている一般的な手法が役立つ場合があります。

2。現在うまく機能している計画を「強制」する必要がありますか?

それは完全にあなた次第です。可能性のあるすべての入力パラメーターに対して適切に機能するクエリプランがあり、クエリストアの機能に慣れていて、クエリプランを強制することによってもたらされる安心を求めている場合は、それを試してください。後退したクエリプランを強制することは、結局のところ、Microsoftが推奨するSQL Server 2016へのアップグレードポリシーの一部です。

16
Joe Obbish