私の同僚のSteveWright(ブログ| @SQL_Steve)は、彼が見た奇妙な結果について最近質問をしてくれました。最新のツールであるSQLSentryPlan Explorer PROの一部の機能をテストするために、彼は幅の広い大きなテーブルを作成し、それに対してさまざまなクエリを実行していました。あるケースでは、彼は大量のデータを返していましたが、 STATISTICS IO
読み取りがほとんど行われていないことを示していました。 #sqlhelpで何人かの人にpingを送信しましたが、この問題は誰も見たことがないようだったので、ブログに載せようと思いました。
TL;DRバージョン
つまり、 STATISTICS IO
に頼ることができないシナリオがいくつかあることに十分注意してください。 正直に言うと。場合によっては(これは TOP
を含みます および並列処理)、論理読み取りを大幅に過少報告します。これにより、そうでない場合でも、非常にI/Oに適したクエリがあると思われる可能性があります。他にも明らかなケースがあります。たとえば、スカラーのユーザー定義関数を使用して大量のI/Oを隠している場合などです。 Plan Explorerを使用すると、これらのケースがより明確になると思います。ただし、これは少し注意が必要です。
問題のクエリ
テーブルには3700万行、1行あたり最大250バイト、約100万ページ、非常に低い断片化(レベル0で0.42%、レベル1で15%、それ以降は0)があります。先頭のINT
にクラスター化された主キーを除いて、計算列、実行中のUDF、およびインデックスはありません。 桁。 TOP
を使用して、500,000行、すべての列を返す単純なクエリ およびSELECT*
:
SET STATISTICS IO ON; SELECT TOP 500000 * FROM dbo.OrderHistory WHERE OrderDate < (SELECT '19961029');
(はい、私は自分のルールに違反していて、 SELECT *
を使用していることに気づきました。 およびTOP
ORDER BY
なし 、ただし、簡単にするために、オプティマイザへの影響を最小限に抑えるように最善を尽くしています。)
結果:
(影響を受ける500000行)テーブル'OrderHistory'。スキャンカウント1、論理読み取り23、物理読み取り0、先読み読み取り0、lob論理読み取り0、lob物理読み取り0、lob先読み読み取り0。
500,000行を返しますが、約10秒かかります。論理読み取り数に問題があることがすぐにわかります。基になるデータについてまだ知らなかったとしても、Management Studioのグリッド結果から、メモリからのものかキャッシュからのものかを問わず、23ページを超えるデータがプルされていることがわかります。これは<のどこかに反映されるはずです。 code> STATISTICS IO 。計画を見て…
…並列処理があり、テーブル全体をスキャンしたことがわかります。では、論理読み取りが23個しかないのはどうしてですか?
別の「同一の」クエリ
スティーブへの最初の質問の1つは、「並列処理を排除するとどうなりますか?」でした。だから私はそれを試してみました。元のサブクエリバージョンを使用して、 MAXDOP 1
を追加しました :
SET STATISTICS IO ON; SELECT TOP 500000 * FROM dbo.OrderHistory WHERE OrderDate < (SELECT '19961029') OPTION (MAXDOP 1);
結果と計画:
(影響を受ける500000行)テーブル'OrderHistory'。スキャンカウント1、論理読み取り149589、物理読み取り0、先読み読み取り0、lob論理読み取り0、lob物理読み取り0、lob先読み読み取り0。
計画は少し複雑ではなく、(明らかな理由で)並列処理がないため、 STATISTICS IO
論理的な読み取りカウントについて、はるかに信頼できる数値を示しています。
真実は何ですか?
これらのクエリの1つが完全な真実を伝えていないことを確認するのは難しいことではありません。 STATISTICS IO
全体像を教えてくれないかもしれませんが、たぶんトレースはそうするでしょう。 Plan Explorerで実際の実行プランを生成してランタイムメトリックを取得すると、魔法の低読み取りクエリは実際には、魔法のピクシーダストのクラウドからではなく、メモリまたはディスクからデータをプルしていることがわかります。実際、他のバージョンよりも*多くの*読み取りがあります:
したがって、読み取りが行われていることは明らかです。読み取りが STATISTICS IO
に正しく表示されていないだけです。 出力。
問題は何ですか?
まあ、正直に言うと、並列処理が確実に役割を果たしているという事実以外はわかりません。それはある種の競合状態のようです。 STATISTICS IO
(そして、それがデータを取得する場所なので、[テーブルI / O]タブ)は非常に誤解を招く読み取り数を示しています。クエリが探しているすべてのデータを返すことは明らかであり、トレース結果から、浸透ではなく読み取りを使用していることが明らかです。 Paul White(ブログ| @SQL_Kiwi)に質問したところ、スレッド前のI / Oカウントの一部のみが合計に含まれていることを示唆しました(これはバグであることに同意します)。
自宅でこれを試してみたい場合は、AdventureWorks(2008、2008 R2、および2012バージョンに対して再現する必要があります)と次のクエリだけが必要です。
SET STATISTICS IO ON; DBCC SETCPUWEIGHT(1000) WITH NO_INFOMSGS; GO SELECT TOP (15000) * FROM Sales.SalesOrderHeader WHERE OrderDate < (SELECT '20080101'); SELECT TOP (15000) * FROM Sales.SalesOrderHeader WHERE OrderDate < (SELECT '20080101') OPTION (MAXDOP 1); DBCC SETCPUWEIGHT(1) WITH NO_INFOMSGS;
( SETCPUWEIGHT
に注意してください 並列処理を同軸にするためにのみ使用されます。詳細については、PlanCostingに関するPaulWhiteのブログ投稿を参照してください。)
結果:
テーブル'SalesOrderHeader'。スキャンカウント1、論理読み取り4、物理読み取り0、先読み読み取り0、lob論理読み取り0、lob物理読み取り0、lob先読み読み取り0。テーブル'SalesOrderHeader'。スキャンカウント1、論理読み取り333、物理読み取り0、先読み読み取り0、lob論理読み取り0、lob物理読み取り0、lob先読み読み取り0。
ポールはさらに簡単な再現を指摘しました:
SET STATISTICS IO ON; GO SELECT TOP (15000) * FROM Production.TransactionHistory WHERE TransactionDate < (SELECT '20080101') OPTION (QUERYTRACEON 8649, MAXDOP 4); SELECT TOP (15000) * FROM Production.TransactionHistory AS th WHERE TransactionDate < (SELECT '20080101');
結果:
テーブル「TransactionHistory」。スキャンカウント1、論理読み取り5、物理読み取り0、先読み読み取り0、lob論理読み取り0、lob物理読み取り0、lob先読み読み取り0。表「TransactionHistory」。スキャンカウント1、論理読み取り110、物理読み取り0、先読み読み取り0、lob論理読み取り0、lob物理読み取り0、lob先読み読み取り0。
したがって、 TOP
を使用すると、これを自由に簡単に再現できるようです。 演算子と十分に低いDOP。バグを報告しました:
- STATISTICS IOは、並列プランの論理読み取りを過少報告します
そして、Paulは、並列処理に関連する他の2つのバグを報告しました。最初のバグは、私たちの会話の結果です。
- ルックアップでのプッシュされた述語によるカーディナリティ推定エラー[関連ブログ投稿]
- 並列処理とトップのパフォーマンスの低下[関連するブログ投稿]
(懐かしいので、ここに私が数年前に指摘した他の6つの並列処理のバグがあります。)
レッスンは何ですか?
単一のソースを信頼することに注意してください。 STATISTICS IO
だけを見る場合 このようにクエリを変更した後、継続時間の増加ではなく、読み取りの奇跡的な減少に焦点を合わせたくなるかもしれません。その時点で、クエリに多大なパフォーマンスの影響を与えたと思って、背中を軽くたたき、早めに仕事を辞めて週末を楽しむことができます。もちろん、真実から遠く離れることはできません。