SQL Server 2008の日時インデックスのパフォーマンスのバグ


11

SQL Server 2008 R2を使用しており、プライマリIDインデックスを持つ非常に大きな(1億行以上)テーブルdatetimeと、非クラスター化インデックスを持つ列があります。私たちは、の使用に基づいていくつかの非常に珍しいクライアント/サーバーの動作を見ているorder by句を、具体的には、インデックス付きのdatetime列

私は次の投稿を読みました:https : //stackoverflow.com/questions/1716798/sql-server-2008-ordering-by-datetime-is-too-slow しかし、クライアント/サーバーでは、現在よりも多くのことが進行中ですここで説明を開始します。

次のクエリを実行すると(一部のコンテンツを保護するために編集されます):

select * 
from [big table] 
where serial_number = [some number] 
order by test_date desc

クエリは毎回タイムアウトします。SQL Serverプロファイラーでは、サーバーに対して実行されたクエリは次のようになります。

exec sp_cursorprepexec @p1 output,@p2 output,NULL,N'select * .....

次に、クエリを次のように変更した場合、

declare @temp int;
select * from [big table] 
where serial_number = [some number] 
order by test_date desc

SQL Serverプロファイラは、実行されたクエリが次のようにサーバーに表示されることを示し、即座に機能します。

exec sp_prepexec @p1 output, NULL, N'declare @temp int;select * from .....

実際には、未使用の宣言ステートメントの代わりに空のコメント( '-;')を挿入して、同じ結果を得ることができます。したがって、最初はこの問題の根本的な原因としてspプリプロセッサをポイントしていましたが、これを行うと:

select * 
from [big table] 
where serial_number = [some number] 
order by Cast(test_date as smalldatetime) desc

これは瞬時に動作し(他のdatetimeタイプとしてキャストできます)、結果をミリ秒で返します。そしてプロファイラーはサーバーへのリクエストを次のように表示します:

exec sp_cursorprepexec @p1 output, @p2 output, NULL, N'select * from .....

そのsp_cursorprepexecため、問題の完全な原因から手順が多少除外されます。これに、sp_cursorprepexec「order by」が使用されない場合にもが呼び出され、その結果も即座に返されるという事実を追加します。

私たちはこの問題をかなり探し回っており、他のユーザーから投稿された同様の問題を目にしていますが、このレベルに分類するものはありません。

他の人がこの動作を目撃したことがありますか?誰かがselectステートメントの前に意味のないSQLを置いて動作を変更するよりも良い解決策を持っていますか?SQL Serverはデータが収集された後に注文を呼び出す必要があるため、これは長い間サーバーに存在するバグのようです。この動作は、大きなテーブルの多くで一貫しており、再現可能であることがわかりました。

編集:

私はまたforceseek、問題を解消するために追加する必要があります。

サーチャーを助けるために追加する必要があります。スローされるODBCタイムアウトエラーは次のとおりです:[Microsoft] [ODBC SQL Server Driver] Operation cancelled

追加10/12/2012:根本的な原因を突き止める(Microsoftに提供するサンプルを作成したことに加えて、私は提出後に結果をここにクロスポストします)。動作中のクエリ(コメント/宣言文が追加されている)と動作していないクエリとの間のODBCトレースファイルを掘り下げています。基本的なトレースの違いを以下に示します。すべてのSQLBindColディスカッションが完了した後、SQLExtendedFetch呼び出しの呼び出しで発生します。呼び出しは戻りコード-1で失敗し、親スレッドはSQLCancelに入ります。これはNative ClientドライバーとLegacy ODBCドライバーの両方で生成できるため、サーバー側の互換性の問題がまだ指摘されています。

(clip)
MSSQLODBCTester 1664-1718   EXIT  SQLBindCol  with return code 0 (SQL_SUCCESS)
        HSTMT               0x001EEA10
        UWORD                       16 
        SWORD                        1 <SQL_C_CHAR>
        PTR                0x03259030
        SQLLEN                    51
        SQLLEN *            0x0326B820 (0)

MSSQLODBCTester 1664-1718   ENTER SQLExtendedFetch 
        HSTMT               0x001EEA10
        UWORD                        1 <SQL_FETCH_NEXT>
        SQLLEN                     1
        SQLULEN *           0x032677C4
        UWORD *             0x032679B0

MSSQLODBCTester 1664-1fd0   ENTER SQLCancel 
        HSTMT               0x001EEA10

MSSQLODBCTester 1664-1718   EXIT  SQLExtendedFetch  with return code -1 (SQL_ERROR)
        HSTMT               0x001EEA10
        UWORD                        1 <SQL_FETCH_NEXT>
        SQLLEN                     1
        SQLULEN *           0x032677C4
        UWORD *             0x032679B0

        DIAG [S1008] [Microsoft][ODBC SQL Server Driver]Operation canceled (0) 

MSSQLODBCTester 1664-1fd0   EXIT  SQLCancel  with return code 0 (SQL_SUCCESS)
        HSTMT               0x001EEA10

MSSQLODBCTester 1664-1718   ENTER SQLErrorW 
        HENV                0x001E7238
        HDBC                0x001E7B30
        HSTMT               0x001EEA10
        WCHAR *             0x08BFFC5C
        SDWORD *            0x08BFFF08
        WCHAR *             0x08BFF85C 
        SWORD                      511 
        SWORD *             0x08BFFEE6

MSSQLODBCTester 1664-1718   EXIT  SQLErrorW  with return code 0 (SQL_SUCCESS)
        HENV                0x001E7238
        HDBC                0x001E7B30
        HSTMT               0x001EEA10
        WCHAR *             0x08BFFC5C [       5] "S1008"
        SDWORD *            0x08BFFF08 (0)
        WCHAR *             0x08BFF85C [      53] "[Microsoft][ODBC SQL Server Driver]Operation canceled"
        SWORD                      511 
        SWORD *             0x08BFFEE6 (53)

MSSQLODBCTester 1664-1718   ENTER SQLErrorW 
        HENV                0x001E7238
        HDBC                0x001E7B30
        HSTMT               0x001EEA10
        WCHAR *             0x08BFFC5C
        SDWORD *            0x08BFFF08
        WCHAR *             0x08BFF85C 
        SWORD                      511 
        SWORD *             0x08BFFEE6

MSSQLODBCTester 1664-1718   EXIT  SQLErrorW  with return code 100 (SQL_NO_DATA_FOUND)
        HENV                0x001E7238
        HDBC                0x001E7B30
        HSTMT               0x001EEA10
        WCHAR *             0x08BFFC5C
        SDWORD *            0x08BFFF08
        WCHAR *             0x08BFF85C 
        SWORD                      511 
        SWORD *             0x08BFFEE6
(clip)

Microsoft Connectケース10/12/2012を追加しました:

https://connect.microsoft.com/SQLServer/feedback/details/767196/order-by-datetime-in-odbc-fails-for-clean-sql-statements#details

また、機能しているクエリと機能していないクエリの両方のクエリプランを検索したことにも注意してください。どちらも実行回数に基づいて適切に再利用されます。キャッシュされたプランをフラッシュして再実行しても、クエリの成功は変わりません。


試してみたらどうなるかselect id, test_date from [big table] where serial_number = ..... order by test_date- SELECT *パフォーマンスに悪影響があるかどうか疑問に思っています。非クラスター化インデックスがオンtest_dateで、クラスター化インデックスがオンになっている場合id(それがその名前であると想定)、このクエリはその非クラスター化インデックスでカバーされているため、非常に迅速に返されます
marc_s

申し訳ありませんが、良い点です。選択した列スペースを( '*'を削除するなど)さまざまな組み合わせで大幅に変更しようとしたことを含める必要がありました。上記の動作は、これらの変更を通じて持続しました。
DBtheDBA

アカウントをそのサイトにリンクしました。モデレーターが投稿をそのサイトに移動したい場合は、どちらにしても問題ありません。私がここに投稿した後、私の開発者の1人がそのサイトを私に指摘しました。
DBtheDBA

ここでどのクライアントスタックが使用されていますか?トレーステキスト全体がなければ、それは問題のようです。元の呼び出しを内部にラップして、sp_executesql何が起こるかを確認してください。
Jon Seigel、

1
スロー実行プランはどのように見えますか?パラメータの盗聴?
マーティン・スミス、

回答:


6

謎はありません。インデックスを使用する明確な選択肢がないため、基本的にランダムに良い(または)本当に(本当に)悪い計画を取得します。ORDER BY句を強制してソートを回避する一方で、datetime列の非クラスター化インデックスは、このクエリには非常に適していません。このクエリのインデックスをより優れたものにするものは、1つになり(serial_number, test_date)ます。さらに良いことに、これはクラスター化インデックスキーの非常に良い候補になります。

経験則として、圧倒的多数のリクエストは特定の時間範囲に関心があるため、時系列は時間列でクラスター化する必要があります。serial_numberの場合のように、データが本質的に選択性の低い列にパーティション化されている場合、この列はクラスター化キー定義の左端の列として追加する必要があります。


ここでは少し混乱しています。なぜ計画はthe order条項に基づいているのですか?where順序付けは行がフェッチされた後にのみ行われるため、計画は条件にそれ自体を制限するべきではありませんか?結果セット全体を取得する前に、サーバーがレコードをソートしようとするのはなぜですか?
DBtheDBA

5
これは、クエリの先頭にコメントを追加すると実行期間に影響する理由も説明していません。
cfradenburg 2012年

また、ほとんどの場合、テーブルはtest_dateではなくシリアル番号でクエリされます。両方に非クラスター化インデックスがあり、テーブルのid列にのみクラスター化されています。これは運用データストアであり、他の列にクラスター化インデックスを追加しても、ページ分割が発生し、パフォーマンスが低下するだけです。
DBtheDBA

1
@DBtheDBA:「バグ」を主張する場合は、適切な調査と開示を行う必要があります。テーブルの正確なスキーマとエクスポートされた統計は、必要なデータベースメタデータのスクリプトを生成する方法に従って、SQL Server 2005およびSQL Server 2008統計のみのデータベース、特にすべての重要なスクリプト統計スクリプト統計とヒストグラムを作成します。これらを問題を再現する手順とともに投稿情報に追加します。
Remus Rusanu 2012年

1
検索の前にそれを読み、あなたの言っていることは理解していますが、サーバーがここで行っていることには根本的な欠陥があります。テーブルとインデックスを再構築し、新しいテーブルで再現しました。再コンパイルオプションは問題を修正しません。これは何かが間違っているという大きなヒントです。すべてにクラスター化インデックスを配置することでこの問題を潜在的に解決できることは間違いありませんが、根本的な原因の解決策ではなく、回避策であり、大きなテーブルでのコストが高くなります。
DBtheDBA

0

バグを再現する方法の詳細を文書化し、connect.microsoft.comに送信します。確認したところ、これに関連するものはすでに何もありませんでした。


再現する環境を作成するために、DBAに明日スクリプトを入力してもらいます。そんなに難しいとは思いません。誰かが自分で試してみたいと思っている人のためにも、ここに投稿します。
DBtheDBA 2012年

開いたときにも接続アイテムを投稿します。そうすれば、他の誰かがこの問題を抱えている場合、彼らはそれに直接指摘されます。そして、この質問を見ている人は誰でもその項目に投票したいと思うかもしれないので、Microsoftはそれに注意を払う可能性が高くなります。
cfradenburg 2012年

0

私の仮説は、クエリプランキャッシュが実行されないことです。(レムスは私と同じことを言っているかもしれませんが、異なる方法で。)

SQLがキャッシュをどのように計画するについての詳細は次のとおりです。

詳細についての説明:特定の[一部の番号]に対して、誰かが以前にそのクエリを実行しました。SQLは、提供された値、関連するテーブル/列などのインデックスと統計情報を調べ、その特定の[いくつかの数値]に対して適切に機能する計画を構築しました。次に、プランをキャッシュして実行し、結果を呼び出し元に返しました。

その後、別の誰かが同じクエリを実行し、[some number]の値が異なります。この特定の値により、結果の行数が大幅に異なり、エンジンはクエリのこのインスタンスに対して異なるプランを作成する必要があります。しかし、それはそのようには機能しません。代わりに、SQLはクエリを取得し、(多かれ少なかれ)クエリキャッシュの大文字と小文字を区別して検索し、既存のクエリのバージョンを探します。以前のものを見つけたら、そのプランを使用します。

アイデアは、それが計画を決定し、それを構築するために必要な時間を節約することです。アイデアの穴は、同じクエリを生成した値を使用して実行されたときである乱暴に異なる結果を。彼らは異なる計画を持つべきですが、そうではありません。最初にクエリを実行した人は誰でも、後でクエリを実行する全員の動作を設定するのに役立ちます。

簡単な例:select * from [people] where lastname = 'SMITH'-非常に人気のある姓GO select * from [people] where lastname = 'BONAPARTE'-よく知らない米国の姓

BONAPARTEの照会を実行すると、SMITH用に作成された計画が再利用されます。SMITHがテーブルスキャンを引き起こした場合(テーブルの行が99%SMITHである場合、これは良いことです)、BONAPARTEもテーブルスキャンを取得します。BONAPARTEがSMITHの前に実行された場合、索引を使用する計画が作成および使用され、SMITHに再度使用される可能性があります(これは、テーブルスキャンの場合に適しています)。テーブル全体を読み取る必要があり、インデックスを読み取ってテーブルにホッピングすることには直接気付かないため、SMITHのパフォーマンスが悪いことに気付かない場合があります。

変更すべきこと、変更すべきことに関しては、SQLはそれをまったく別のクエリと見なしており、[いくつかの数値]の値に固有の新しいプランを構築していると思います。

これをテストするには、FORとテーブル名の間にスペースを追加するなど、クエリに無意味な変更を加えるか、最後にコメントを付けます。速いですか?もしそうなら、それはそのクエリがキャッシュにあるものと少し異なるため、SQLは「新しい」クエリに対して何をするかをしました。

解決策として、私は3つのことを検討します。まず、統計が最新であることを確認します。これは、クエリが奇妙またはランダムに動作するように思われる場合に、最初に行うべきです。DBAがこれを行う必要がありますが、事態が発生します。最新の統計を確認する通常の方法は、テーブルのインデックスを再作成することです。これは必ずしも軽量なことではありませんが、統計を更新するだけのオプションもあります。

次に考えることは、Remusの提案に沿ってインデックスを追加することです。より良い/異なるインデックスを使用すると、ある値と別の値がより安定し、それほど大きく変動しない可能性があります。

それでも問題が解決しない場合は、ステートメントを実行するたびにRECOMPILEキーワードを使用して、新しいプランを強制的に実行する必要があります。

select * from [big table] where serial_number = [some number] order by test_date desc OPTION(RECOMPILE)

ここに同様の状況を説明する記事があります。率直に言って、以前RECOMPILEがストアドプロシージャに適用されるのを見ただけですが、 "通常の" SELECTステートメントで動作するようです。キンバリー・トリップは私を間違った方向に導いたことはありません。

計画ガイド」と呼ばれる機能を調べることもできますが、それはより複雑でやり過ぎかもしれません。


これらの懸念のいくつかをカバーするには:1.統計が更新され、更新されています。2.いくつかの方法(インデックスのカバーなど)でインデックス作成を試みましたが、問題はorder by特に日時インデックスに対する使用法に関連しているようです。3. RECOMPILEオプションを使用してアイデアを試してみましたが、それでも失敗しました。少し驚いたのですが、それが機能することを期待していましたが、それが本番環境のソリューションであるかどうかはわかりません。
DBtheDBA
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.