実行計画のコストの割合に頼りすぎないでください。これらは、行カウントなどの「実際の」数値を使用した実行後プランであっても、常に推定コストです。推定コストは、同じクエリに対して異なる候補実行プランからオプティマイザが選択できるようにすることを目的とする目的で偶然よく機能するモデルに基づいています。コスト情報は興味深いものであり、考慮すべき要素ですが、クエリチューニングの主要なメトリックになることはめったにありません。実行計画情報を解釈するには、提示されたデータのより広い視野が必要です。
ItemTranクラスター化インデックスシーク演算子
この演算子は、実際には2つの操作が1つになっています。最初にインデックスシーク操作により、述語product_code_v42 = 'M10BOLT'
に一致するすべての行が検索され、次に各行に残余述語がbit_field_v41 & 4 = 0
適用されます。bit_field_v41
ベースタイプ(tinyint
またはsmallint
)からへの暗黙的な変換がありinteger
ます。
ビットごとのAND演算子(&)では、両方のオペランドが同じ型である必要があるため、変換が発生します。定数値「4」の暗黙の型は整数であり、データ型の優先順位規則は、優先度の低いbit_field_v41
フィールド値が変換されることを意味します。
この問題は、述部を次のように記述することで簡単に修正されbit_field_v41 & CONVERT(tinyint, 4) = 0
ます。つまり、定数値の優先順位は低く、列値ではなく(定数の畳み込み中に)変換されます。bit_field_v41
がtinyint
まったく変換されない場合。同様に、CONVERT(smallint, 4)
場合に使用することができbit_field_v41
ていますsmallint
。それは、言った変換は、パフォーマンスの問題ではありません。この場合、まだ種類が一致し、可能な場合は暗黙の型変換を避けることをお勧めします。
このシークの推定コストの大部分は、ベーステーブルのサイズまでです。クラスター化インデックスキー自体はかなり狭いですが、各行のサイズは大きくなります。テーブルの定義は指定されていませんが、ビューで使用される列だけがかなりの行幅になります。クラスター化インデックスにはすべての列が含まれるため、クラスター化インデックスキー間の距離は、インデックスキーの幅ではなく、行の幅になります。一部の列でバージョンサフィックスを使用すると、実際のテーブルには以前のバージョンの列がさらに多くあることが示唆されます。
シーク、残余述語、および出力列を見ると、同等のクエリを作成することにより、この演算子のパフォーマンスを個別に確認できます(1 <> 2
自動パラメーター化を防ぐための秘isです。矛盾はオプティマイザーによって削除され、クエリプラン):
SELECT
it.booking_no_v32,
it.QtyCheckedOut,
it.QtyReturned,
it.Trans_qty,
it.trans_type_v41
FROM dbo.tblItemTran AS it
WHERE
1 <> 2
AND it.product_code_v42 = 'M10BOLT'
AND it.bit_field_v41 & CONVERT(tinyint, 4) = 0;
先読みはテーブル(クラスター化インデックス)の断片化の影響を受けるため、コールドデータキャッシュを使用したこのクエリのパフォーマンスは興味深いものです。このテーブルのクラスタリングキーは断片化を招くため、このインデックスを定期的に維持(再編成または再構築)し、適切なインデックスを使用して、FILLFACTOR
インデックスメンテナンスウィンドウ間で新しい行のスペースを確保することが重要です。
SQL Data Generatorを使用して生成されたサンプルデータを使用して、先読みに対する断片化の影響のテストを実行しました。質問のクエリプランに示されているのと同じテーブルの行数を使用すると、非常に断片化されたクラスター化インデックスはのSELECT * FROM view
後に15秒かかりましたDBCC DROPCLEANBUFFERS
。ItemTransテーブルで新しく再構築されたクラスター化インデックスを使用した同じ条件での同じテストは、3秒で完了しました。
通常、テーブルデータが完全にキャッシュ内にある場合、断片化の問題はそれほど重要ではありません。ただし、断片化が少ない場合でも、テーブルの行が広いということは、論理読み取りおよび物理読み取りの数が予想よりはるかに多いことを意味する場合があります。また、明示的なものの追加と削除を試してCONVERT
、ベストプラクティス違反を除き、ここで暗黙的な変換の問題は重要ではないという私の期待を検証することもできます。
さらに重要なのは、シーク演算子から出る行の推定数です。最適化時間の見積もりは165行ですが、実行時に4,226が生成されました。後でこの点に戻りますが、不一致の主な理由は、残留述語の選択性(ビット単位のANDを含む)がオプティマイザーで予測するのが非常に難しいことです-実際、推測に頼っています。
フィルター演算子
ここでは、主に2つのNOT IN
リストがどのように組み合わされ、単純化され、次に展開されるかを説明し、次のハッシュマッチの説明の参照を提供するために、フィルター述語を示します。シークからのテストクエリを展開して、その効果を組み込み、フィルターオペレーターのパフォーマンスへの影響を判断できます。
SELECT
it.booking_no_v32,
it.trans_type_v41,
it.Trans_qty,
it.QtyReturned,
it.QtyCheckedOut
FROM dbo.tblItemTran AS it
WHERE
it.product_code_v42 = 'M10BOLT'
AND it.bit_field_v41 & CONVERT(tinyint, 4) = 0
AND
(
(
it.trans_type_v41 NOT IN (2, 3, 6, 7, 18, 19, 20, 21, 12, 13, 22)
AND it.trans_type_v41 NOT IN (6, 7)
)
OR
(
it.trans_type_v41 NOT IN (6, 7)
)
OR
(
it.trans_type_v41 IN (6, 7)
AND it.QtyCheckedOut = 0
)
OR
(
it.trans_type_v41 IN (6, 7)
AND it.QtyCheckedOut > 0
AND it.trans_qty - (it.QtyCheckedOut - it.QtyReturned) > 0
)
);
プランのCompute Scalarオペレーターは、次の式を定義します(計算自体は、後のオペレーターが結果を必要とするまで延期されます)。
[Expr1016] = (trans_qty - (QtyCheckedOut - QtyReturned))
ハッシュ一致演算子
文字データ型で結合を実行することは、この演算子の推定コストが高くなる理由ではありません。SSMSツールチップにはハッシュキープローブエントリのみが表示されますが、重要な詳細は[SSMSプロパティ]ウィンドウにあります。
ハッシュ一致演算子はbooking_no_v32
、ItemTranテーブルの列の値(Hash Keys Build)を使用してハッシュテーブルを作成booking_no
し、Bookingsテーブルの列(Hash Keys Probe)を使用して一致をプローブします。SSMSツールチップには通常、プローブ残余も表示されますが、テキストはツールチップには長すぎて、単に省略されます。
プローブ残差は、インデックスシークが先に行われた後に見られる残差に似ています。ハッシュに一致するすべての行で残余述語が評価され、行を親演算子に渡す必要があるかどうかが決定されます。バランスのとれたハッシュテーブルでハッシュ一致を見つけるのは非常に高速ですが、一致する各行に複雑な残余述語を適用することは、比較すると非常に遅くなります。Plan ExplorerのHash Matchツールチップには、Probe Residual式などの詳細が表示されます。
残りの述語は複雑で、予約テーブルから列が利用できるようになったため、予約進行状況のチェックが含まれています。また、ツールヒントには、インデックスシークの初期段階で見られた推定行数と実際の行数の不一致が表示されます。フィルタリングの多くが2回実行されるのは奇妙に思えるかもしれませんが、これはオプティマイザが楽観的であるだけです。プローブの残差からプランを押し下げて行を削除できるフィルターの部分は期待していません(行カウントの推定値はフィルターの前後で同じです)が、オプティマイザーはそれが間違っている可能性があることを知っています。行を早期にフィルタリングする(ハッシュ結合のコストを削減する)機会は、追加のフィルターのわずかなコストに見合うだけの価値があります。予約テーブルの列のテストが含まれているため、フィルター全体をプッシュダウンすることはできませんが、ほとんどは可能です。
ハッシュテーブルに予約されているメモリの量は推定される行数に基づいているため、過小評価されている行数はハッシュ一致演算子の問題です。実行時に必要なハッシュテーブルのサイズに対してメモリが小さすぎる場合(行数が多いため)、ハッシュテーブルは再帰的に物理tempdbストレージに流出し、多くの場合パフォーマンスが非常に低下します。最悪の場合、実行エンジンはハッシュバケットの再帰的な流出を停止し、非常に遅い救済アルゴリズム。ハッシュの流出(再帰的または救済)は、質問で説明されているパフォーマンスの問題の最も可能性の高い原因です(文字型の結合列または暗黙的な変換ではありません)。根本的な原因は、誤った行数(カーディナリティー)の推定に基づいて、サーバーが予約するメモリーが少なすぎることです。
悲しいことに、SQL Server 2012より前のバージョンでは、ハッシュオペレーションがメモリ割り当て(サーバーに空きメモリが大量にある場合でも、実行開始前に予約された後は動的に成長できない)を超えて、 tempdb。プロファイラーを使用してハッシュ警告イベントクラスを監視することは可能ですが、警告を特定のクエリと関連付けることは困難です。
問題を修正する
3つの問題は、断片化、ハッシュマッチ演算子の複雑なプローブの残余、およびインデックスシークでの推測に起因する誤ったカーディナリティ推定です。
推奨ソリューション
断片化を確認し、必要に応じて修正し、メンテナンスをスケジュールして、インデックスが適切に編成された状態を保つようにします。カーディナリティの推定値を修正する通常の方法は、統計を提供することです。この場合、オプティマイザーには組み合わせ(product_code_v42
、bitfield_v41 & 4 = 0
)の統計が必要です。式の統計を直接作成することはできないため、最初にビットフィールド式の計算列を作成してから、手動で複数列の統計を作成する必要があります。
ALTER TABLE dbo.tblItemTran
ADD Bit3 AS bit_field_v41 & CONVERT(tinyint, 4);
CREATE STATISTICS [stats dbo.ItemTran (product_code_v42, Bit3)]
ON dbo.tblItemTran (product_code_v42, Bit3);
計算列のテキスト定義は、使用する統計についてビュー定義のテキストとほぼ正確に一致する必要があるため、暗黙的な変換を排除するようにビューを修正すると同時に、テキストの一致を確認するように注意する必要があります。
複数列の統計により、推定値が大幅に向上し、ハッシュ一致演算子が再帰的スピルまたは救済アルゴリズムを使用する可能性が大幅に減少するはずです。計算列(メタデータのみの操作であり、マークされていないためテーブル内にスペースを必要としませんPERSISTED
)と複数列の統計を追加することは、最初のソリューションでの最良の推測です。
クエリのパフォーマンスの問題を解決する場合、経過時間、CPU使用率、論理読み取り、物理読み取り、待機の種類、期間などを測定することが重要です。上記のように、疑わしい原因を検証するためにクエリの一部を個別に実行することも役立ちます。
データの最新のビューが重要ではない環境では、ビュー全体をスナップショットテーブルに頻繁に具体化するバックグラウンドプロセスを実行すると便利な場合があります。このテーブルは、通常の基本テーブルであり、更新パフォーマンスへの影響を心配することなく、読み取りクエリ用にインデックスを作成できます。
インデックス作成を表示
元のビューに直接インデックスを付けようとしないでください。読み取りパフォーマンスは驚くほど高速(ビューインデックスでの単一シーク)になりますが、(この場合)既存のクエリプランのすべてのパフォーマンスの問題は、ビューで参照されるテーブル列を変更するクエリに転送されます。実際、ベーステーブルの行を変更するクエリは、非常に大きな影響を受けます。
部分的なインデックス付きビューを使用した高度なソリューション
この特定のクエリには、カーディナリティの推定値を修正し、フィルターとプローブの残余を削除する部分的なインデックス付きビューソリューションがありますが、データに関するいくつかの仮定に基づいており(主にスキーマでの推測)、特に適切なことに関して専門家による実装が必要ですインデックス付きビューのメンテナンスプランをサポートするインデックス。以下のコードを共有します。分析とテストを慎重に行わない限り、実装することはお勧めしません。
-- Indexed view to optimize the main view
CREATE VIEW dbo.V1
WITH SCHEMABINDING
AS
SELECT
it.ID,
it.product_code_v42,
it.trans_type_v41,
it.booking_no_v32,
it.Trans_qty,
it.QtyReturned,
it.QtyCheckedOut,
it.QtyReserved,
it.bit_field_v41,
it.prep_on,
it.From_locn,
it.Trans_to_locn,
it.PDate,
it.FirstDate,
it.PTimeH,
it.PTimeM,
it.RetnDate,
it.BookDate,
it.TimeBookedH,
it.TimeBookedM,
it.TimeBookedS,
it.del_time_hour,
it.del_time_min,
it.return_to_locn,
it.return_time_hour,
it.return_time_min,
it.AssignTo,
it.AssignType,
it.InRack
FROM dbo.tblItemTran AS it
JOIN dbo.tblBookings AS tb ON
tb.booking_no = it.booking_no_v32
WHERE
(
it.trans_type_v41 NOT IN (2, 3, 7, 18, 19, 20, 21, 12, 13, 22)
AND it.trans_type_v41 NOT IN (6, 7)
AND it.bit_field_v41 & CONVERT(tinyint, 4) = 0
)
OR
(
it.trans_type_v41 NOT IN (6, 7)
AND it.bit_field_v41 & CONVERT(tinyint, 4) = 0
AND tb.BookingProgressStatus = 1
)
OR
(
it.trans_type_v41 IN (6, 7)
AND it.bit_field_v41 & CONVERT(tinyint, 4) = 0
AND it.QtyCheckedOut = 0
)
OR
(
it.trans_type_v41 IN (6, 7)
AND it.bit_field_v41 & CONVERT(tinyint, 4) = 0
AND it.QtyCheckedOut > 0
AND it.trans_qty - (it.QtyCheckedOut - it.QtyReturned) > 0
);
GO
CREATE UNIQUE CLUSTERED INDEX cuq ON dbo.V1 (product_code_v42, ID);
GO
既存のビューは、上記のインデックス付きビューを使用するように調整されました。
CREATE VIEW [dbo].[vwReallySlowView2]
AS
SELECT
I.booking_no_v32 AS bkno,
I.trans_type_v41 AS trantype,
B.Assigned_to_v61 AS Assignbk,
B.order_date AS dateo,
B.HourBooked AS HBooked,
B.MinBooked AS MBooked,
B.SecBooked AS SBooked,
I.prep_on AS Pon,
I.From_locn AS Flocn,
I.Trans_to_locn AS TTlocn,
CASE I.prep_on
WHEN 'Y' THEN I.PDate
ELSE I.FirstDate
END AS PrDate,
I.PTimeH AS PrTimeH,
I.PTimeM AS PrTimeM,
CASE
WHEN I.RetnDate < I.FirstDate
THEN I.FirstDate
ELSE I.RetnDate
END AS RDatev,
I.bit_field_v41 AS bitField,
I.FirstDate AS FDatev,
I.BookDate AS DBooked,
I.TimeBookedH AS TBookH,
I.TimeBookedM AS TBookM,
I.TimeBookedS AS TBookS,
I.del_time_hour AS dth,
I.del_time_min AS dtm,
I.return_to_locn AS rtlocn,
I.return_time_hour AS rth,
I.return_time_min AS rtm,
CASE
WHEN
I.Trans_type_v41 IN (6, 7)
AND I.Trans_qty < I.QtyCheckedOut
THEN 0
WHEN
I.Trans_type_v41 IN (6, 7)
AND I.Trans_qty >= I.QtyCheckedOut
THEN I.Trans_Qty - I.QtyCheckedOut
ELSE
I.trans_qty
END AS trqty,
CASE
WHEN I.Trans_type_v41 IN (6, 7)
THEN 0
ELSE I.QtyCheckedOut
END AS MyQtycheckedout,
CASE
WHEN I.Trans_type_v41 IN (6, 7)
THEN 0
ELSE I.QtyReturned
END AS retqty,
I.ID,
B.BookingProgressStatus AS bkProg,
I.product_code_v42,
I.return_to_locn,
I.AssignTo,
I.AssignType,
I.QtyReserved,
B.DeprepOn,
CASE B.DeprepOn
WHEN 1 THEN B.DeprepDateTime
ELSE I.RetnDate
END AS DeprepDateTime,
I.InRack
FROM dbo.V1 AS I WITH (NOEXPAND)
JOIN dbo.tblbookings AS B ON
B.booking_no = I.booking_no_v32
JOIN dbo.tblInvmas AS M ON
I.product_code_v42 = M.product_code;
クエリと実行計画の例:
SELECT
vrsv.*
FROM dbo.vwReallySlowView2 AS vrsv
WHERE vrsv.product_code_v42 = 'M10BOLT';
新しいプランでは、ハッシュマッチには残余述語がなく、インデックス付きビューシークに複雑なフィルター、残余述語がなく、カーディナリティの推定値は正確です。
挿入/更新/削除計画がどのように影響を受けるかの例として、これはItemTransテーブルへの挿入の計画です。
強調表示されたセクションは新しく、インデックス付きビューのメンテナンスに必要です。テーブルスプールは、インデックス付きビューのメンテナンスのために、挿入されたベーステーブルの行を再生します。各行はクラスター化インデックスシークを使用して予約テーブルに結合され、フィルターは複雑なWHERE
句の述語を適用して、行をビューに追加する必要があるかどうかを確認します。その場合、ビューのクラスター化インデックスに対して挿入が実行されます。
SELECT * FROM view
以前に実行された同じテストは、インデックス付きビューを使用して150ミリ秒で完了しました。
最後に、2008 R2サーバーがまだRTMにあることに気付きました。パフォーマンスの問題を解決することはできませんが、2008 R2のService Pack 2は 2012年7月から提供されており、サービスパックをできるだけ最新の状態に保つ多くの理由があります。