web-dev-qa-db-ja.com

SQLクエリは、特にページ分割のためのオフセットとフェッチで実行に時間がかかります

これは、レポートに対して実行しているクエリです。 offsetfetchなしで実行するには、約20秒かかります。この2つでは、クエリは10分で完了しません。

select *
-- h.ActionDate as Date, h.Action, h.Actor , h.PaymentLotNo 
-- , sh.OwnerName as OwnersName,sh.TECHID as TechId, sh.Id as SubsidyCardId, sh.TotalSubsidy as SubsidyAmount
-- ,a.ToleEng + '-' + v.NameEng + cast(a.Ward as nvarchar(4)) + ',' + d.NameEng + ',' + s.StateNameEng as Address
from common.CTB_SUBSIDY_APPLICATION_HISTORY h 
join fsams.CTB_SUBSIDY_CARD_HEADER sh on sh.LotNumber = h.PaymentLotNo 
join fsams.CTB_SUBSIDY_CARD_ADDRESS sa on sa.SubsidyId = sh.Id 
join common.CTB_ADDRESS a on a.AddressID = sa.AddressId 
join COMMON.CLK_STATE s on s.StateId = a.StateID 
join COMMON.CLK_DISTRICTS d on d.DistrictCd = a.DistrictCD 
join common.CLK_DISTRICT_VDC v on v.VdcCd = a.VdcCd where 1 = 1 order by h.ActionDate desc
offset 790 rows 
FETCH first 10 row only

以下は、クエリの 実行プラン です。

Execution Plan

このクエリを最適化するにはどうすればよいですか?

1
Bibek Shah

_OFFSET...FETCH_を使用すると、行の目標が導入される可能性があります(TOPがそうするのと同じように、 実行計画での行の目標の設定と識別 を参照)その主題の詳細については、Paul Whiteを参照してください)。これが、クエリのその部分なしでこのような違いが見られる理由です。行の目標がないと、計画が完全に変わります。

行の目標により、SQL Serverは、共有した実行プランで一連のネストされたループ結合を選択します。これは通常は問題ありませんが、結合は推定値よりも制限が厳しいため、要求された10行を取得するのに予想よりもかなり時間がかかると思います。

クエリの最後にOPTION (QUERYTRACEON 4138)を追加して再度実行することで、この理論をテストできます。


適切なソリューションに関する限り-Dan Guzmanが示唆したように、_fsams.CTB_SUBSIDY_CARD_HEADER_に狭いインデックスを作成してみることができます。

_CREATE NONCLUSTERED INDEX IX_PaymentLotNo
ON FSAMS.CTB_SUBSIDY_CARD_HEADER (PaymentLotNo)
INCLUDE (OwnerName, TECHID, Id, TotalSubsidy)
_

また、実際のクエリの列リストと比較して、最終的に得られる最終的な計画が確実に変更されるため、パフォーマンス関連のテストに_SELECT *_を使用しないでください。

クエリの推定値をスローする可能性がある暗黙的な変換が行われています-LotNumberは_CTB_SUBSIDY_CARD_HEADER_テーブルに文字列として保存され、intに変換されてから_CTB_SUBSIDY_APPLICATION_HISTORY_のPaymentLotNoに参加しました。可能であれば、これらの列をALTER一致するデータ型にする必要があります。


補足:サポートされていないSQL Server 2014 RTMを実行しています。最新のサービスパックにアップグレードする必要があります。また、発生している問題couldはその後修正されたオプティマイザのバグに関連しているため、その時点でTF 4199を有効にすることを検討してください。

2
Josh Darnell

Dan Guzmanが言ったように、fsams.CTB_SUBSIDY_CARD_HEADER(LotNumber)にインデックスを追加する必要があります。このテーブルのクラスター化インデックススキャンは、クエリのコストの59%を表します。

Clustered scan

また、sh.LogNumberとh.PaymentLotNoは同じタイプではないようです。 sh.LotNumberの計画で暗黙の変換警告が表示されます。修正する必要があります。

implicit conversion

そして、あなたはSELECT *なしで新しい計画を貼り付ける必要があります

クエリを実行すると、見積もりプランだけでなく実際の実行プランも取得できます。このようにして、推定行数と実際の行数が一致するかどうか、統計が古くなっていないかどうかを確認することもできます。

まず、クエリのwhere 1=1も明確ではありません。

すべての提案とは別に、指定されたクエリを改善する主要なスコープが1つあります。

メインクエリのみにPagingを適用します。ページングの行数に影響を与えるメインクエリ。

メインクエリがよくわからない場合は、例を挙げて説明します。

;With CTE as
    (
    select 
  h.ActionDate as Date, h.Action, h.Actor , h.PaymentLotNo 
  , sh.OwnerName as OwnersName
  ,sh.TECHID as TechId
  , sh.Id as SubsidyCardId
  , sh.TotalSubsidy as SubsidyAmount
  ,a.ToleEng + '-' + v.NameEng + cast(a.Ward as nvarchar(4)) + ',' + d.NameEng + ',' + s.StateNameEng as Address
 from fsams.CTB_SUBSIDY_CARD_HEADER sh 
 join common.CTB_SUBSIDY_APPLICATION_HISTORY h on sh.LotNumber = h.PaymentLotNo 
 join fsams.CTB_SUBSIDY_CARD_ADDRESS sa on sa.SubsidyId = sh.Id 

 where 1 = 1 order by h.ActionDate desc
 offset 790 rows 
    FETCH first 10 row only
    )

select 
 sa.*
 from CTE sa 
 join common.CTB_ADDRESS a on a.AddressID = sa.AddressId
 join COMMON.CLK_STATE s on s.StateId = a.StateID 
 join COMMON.CLK_DISTRICTS d on d.DistrictCd = a.DistrictCD 
 join common.CLK_DISTRICT_VDC v on v.VdcCd = a.VdcCd 

最終結果セットのMasterテーブルを常に結合します。

それが明らかで、可能であれば、CTE内の結合からいくつかのテーブルを削除して、最終的なクエリに含めることができます。

CTEの結果をTempテーブルに入れます。

0
KumarHarsh