web-dev-qa-db-ja.com

複雑なクエリの最適化-100%コストのインデックスシーク?

私はC#開発者ですが、SQLの経験は豊富です。クエリの最適化について少し経験がありますが、困ったものに出くわしました。

クエリは、約20,000行の実行に約3分かかります。行はすぐにストリーミングを開始しますが、3分間、SQLサーバーのCPUは100%で固定されます。

この特定のケースでは、クライアントが20k行のテストを要求するまで、私のクエリはうまく機能しました。これらの2万行(クライアント)は、20名の実務家(それぞれ約1000名)に分散されました。

これが、関連するテーブルのデータベーススキーマです。

enter image description here

クエリは、再帰CTEを使用して、ソースプラクティショナーを指定すると、その下のクライアントの(nレベル)ツリーを収集します。次に、いくつかのFKタイプのテーブルに結合して、追加情報を収集します。

スローダウンの原因は、結合から権限テーブルへの移動であり、どの実務家がどのクライアントを管理しているかを表しています。

enter image description here

pooling.ClientAccountInheritedRateGrouppooling.ClientAccountFirstContactおよびpooling.PractitionerAccountFirstContactはすべてのビューで、すべてのデータセットに対して1秒未満の選択で戻ります。

さて、実際のクエリについて(申し訳ありませんが、少し怪物です):

with h (distance, id, name, parentid, [path]) as ( 
select 
    0,
    pa.id,
    pa.name,
    null,
    convert(varchar(80), ltrim(str(pa.id))) as node_seq
from     
    pooling.practitioneraccount pa
    left outer join pooling.practitioneraccountrelation as par
        on pa.id = par.childid
where  
    pa.id = @PractitionerAccountId
    or (@PractitionerAccountId is null and par.parentid is null)

union all

select 
    1,
    -1,
    'Unmanaged Clients',
    -1,
    '-1'
where @practitioneraccountid is null

union all

select   
    distance + 1,
    pa.id,
    pa.name,
    par.parentid,
    convert(varchar(80), h.path + ',' + ltrim(str(pa.id)))
from     
    pooling.practitioneraccount pa
    inner join pooling.practitioneraccountrelation as par
        on pa.id = par.childid
    inner join h on par.parentid = h.id
)

select  
    distance as Depth,
    h.id as PractitionerAccountId,
    h.name as PractitionerAccountName,
    pafc.FullName as PractitionerAccountContactName,
    h.parentid as PractitionerAccountParentId,
    path as PractitionerAccountPath,
    ca.id as ClientAccountId,
    ca.name as ClientAccountName,
    ca.irdnumber as ClientAccountIrdNumber,
    bd.id as ClientAccountBalanceDateId,
    bd.day as ClientAccountBalanceDateDay,
    bd.month as ClientAccountBalanceDateMonth,
    cafc.FirstName as ClientAccountContactFirstName,
    cafc.LastName as ClientAccountContactLastName,
    cafc.PhoneNumber as ClientAccountContactPhoneNumber,
    cafc.MobileNumber as ClientAccountContactMobileNumber,
    ca.TownOrCity as ClientAccountLocation,
    rr.Rate as ClientAccountRwtRate,
    rr.Id as ClientAccountRwtRateId,
    cairg.RateGroupId as ClientAccountInheritedRateGroupId,
    cairg.RateGroupName as ClientAccountInheritedRateGroupName,
    ag.id as ClientAccountAssociatedGroupId,
    ag.name as ClientAccountAssociatedGroupName,
    agca.uniqueid as ClientAccountAssociatedGroupUniqueId,
    agca.approved as ClientAccountAssociatedGroupApproved
from    
    h
    left outer join security.practitioneraccountauthority as paa 
        on (h.id = paa.practitioneraccountid)
    left outer join pooling.clientaccount as ca 
        on ( 
            paa.clientaccountid = ca.id 
            or (h.id = -1 and ca.id not in (
                select clientaccountid 
                from security.practitioneraccountauthority
                )
            )
        )
left outer join config.RwtRate as rr  on ca.RwtRateId = rr.Id
left outer join config.balancedate as bd on (ca.balancedateid = bd.id)
left outer join pooling.ClientAccountInheritedRateGroup as cairg 
    on (ca.id = cairg.ClientAccountId)
left outer join security.associatedgroupclientaccount as agca 
    on (ca.id = agca.clientaccountid)
left outer join security.associatedgroup as ag 
    on (agca.associatedgroupid = ag.id)
left outer join pooling.ClientAccountFirstContact as cafc 
    on (cafc.ClientAccountId = ca.Id) 
left outer join pooling.PractitionerAccountFirstContact as pafc 
    on (pafc.PractitionerAccountId = paa.PractitionerAccountId)

クエリのCTEにより、開業医のツリー全体が得られます。開業医は、権限テーブルを介して、その下のクライアントに結合されます。クエリのCTE部分はほぼ瞬時に返されますが、クライアントへの参加を開始したときにのみ、パフォーマンスの問題が発生します。

権限テーブルのClientAccountId列にインデックス(および統計)を追加しました

CREATE NONCLUSTERED INDEX [IX_PractitionerAccountAuthority_ClientAccountId]
     ON     [security].[PractitionerAccountAuthority] 
(
    [ClientAccountId] ASC
)

上記のビューの定義など、追加情報が必要かどうかを尋ねてください。

6
agrath

それはこれであることが判明しました:

 left outer join pooling.clientaccount as ca 
        on ( 
            paa.clientaccountid = ca.id 
            or (h.id = -1 and ca.id not in (
                select clientaccountid 
                from security.practitioneraccountauthority
                )
            )
        )

On条件に入力データに応じて複数のパスがあったため、大幅な速度低下が発生したため、最適化できませんでした。

0
agrath

問題の説明は次のとおりです。「クエリのCTE部分はほぼ瞬時に返されます。クライアントへの参加を開始したときにのみ、パフォーマンスの問題が発生します。」.

考えられる1つの可能性のある説明は次のとおりです。オプティマイザはサブツリーのカーディナリティの推定に失敗し、非効率的な計画を選択します。階層を保存するための方法として、これは当然のことです。実際に取得せずに、サブツリーのサイズをどのように推定しますか?

マテリアライズドパスを使用できますか?実体化されたパスを使用してサブツリーを取得することは、基本的に1つの範囲スキャンであり、高速かつ単純であり、オプティマイザは1つのインデックスの統計から適切なカーディナリティを推定できます。

私の経験では、階層を格納/読み取る方法は拡大しません。私はそれを高速に動作させ、リソースを効率的に使用することはできませんでした。

2
A-K