2

DB に負荷が発生した後、クエリの 1 つが低下しました。

私たちのクエリは、3 つのテーブル間の結合です。

  1. Base10 M 行を含むテーブル。
  2. EventPerson5000行を含むテーブル。
  3. EventPerson788空です。

EventPersonオプティマイザーは、シークではなく、問題を再現するためのスクリプトでインデックスをスキャンしているようです。

--Create Tables 
CREATE TABLE [dbo].[BASE](
        [ID] [bigint] NOT NULL,
        [IsActive] BIT
       PRIMARY KEY CLUSTERED ([ID] ASC) 
)ON [PRIMARY]
GO

CREATE TABLE [dbo].[EventPerson](
    [DUID] [bigint] NOT NULL,
    [PersonInvolvedID] [bigint] NULL,

PRIMARY KEY CLUSTERED ([DUID] ASC)
) ON [PRIMARY]
GO

CREATE NONCLUSTERED INDEX [EventPerson_IDX] ON [dbo].[EventPerson]
(
    [PersonInvolvedID] ASC
)

CREATE TABLE [dbo].[EventPerson788](
    [EntryID] [bigint] NOT NULL,
    [LinkedSuspectID] [bigint] NULL,
    [sourceid] [bigint] NULL,

    PRIMARY KEY CLUSTERED ([EntryID] ASC)
) ON [PRIMARY]
GO

ALTER TABLE [dbo].[EventPerson788] WITH CHECK 
ADD CONSTRAINT [FK7A34153D3720F84A] 
FOREIGN KEY([sourceid]) REFERENCES [dbo].[EventPerson] ([DUID])
GO

ALTER TABLE [dbo].[EventPerson788] CHECK CONSTRAINT [FK7A34153D3720F84A]
GO

CREATE NONCLUSTERED INDEX [EventPerson788_IDX] 
ON [dbo].[EventPerson788] ([LinkedSuspectID] ASC)
GO

--POPOLATE BASE TABLE 
DECLARE @I BIGINT=1 
WHILE (@I<10000000) 
    BEGIN
    begin transaction
    INSERT INTO BASE(ID) VALUES(@I) 
    SET @I+=1
        if (@I%10000=0 )
        begin
    commit;
        end;

    END
go

--POPOLATE EventPerson TABLE 
DECLARE @I BIGINT=1 
WHILE (@I<5000) 
    BEGIN
    BEGIN TRANSACTION
    INSERT INTO EventPerson(DUID,PersonInvolvedID) VALUES(@I,(SELECT TOP 1 ID FROM BASE ORDER BY NEWID())) 
        SET @I+=1
        IF(@I%10000=0 )
            COMMIT TRANSACTION ;
    END

GO 

これはクエリです:

select 
    count(EventPerson.DUID) 
from 
    EventPerson  
inner loop join 
    Base on EventPerson.DUID = base.ID 
left outer join 
    EventPerson788 on EventPerson.DUID = EventPerson788.sourceid 
where 
    (EventPerson.PersonInvolvedID = 37909 or 
     EventPerson788.LinkedSuspectID = 37909) 
    AND BASE.IsActive = 1

オプティマイザーがインデックス シークの代わりにインデックス スキャンを使用することを決定した理由がわかりましたか?

すでに行われている回避策:

  • テーブルを分析し、統計を作成します。
  • インデックスを再構築します。
  • FORCESEEK ヒントを試す

EventPerson上記のいずれも、オプティマイザーがベース テーブルに対して インデックス シークとシークを実行するよう説得するものではありませんでした。

ご協力いただきありがとうございます 。

4

2 に答える 2

2

or条件と に対する外部結合のために、スキャンが存在しEventPerson788ます。

where に行が存在するEventPersonときEventPerson.PersonInvolvedID = 37909 またはそのときから行を返します。最後の部分は、すべての行を結合に対してチェックする必要があることを意味します。EventPerson788EventPerson788.LinkedSuspectID = 37909EventPerson

EventPerson788に一致する行がある可能性がある場合に後で再利用するためにクエリ プランが保存されるため、クエリ オプティマイザは空であるという事実を使用できませんEventPerson788

アップデート:

または のシークを取得する代わりに、共用体を使用してクエリを書き直すことができますEventPerson

select count(EventPerson.DUID) 
from 
  (
    select EventPerson.DUID
    from EventPerson
    where EventPerson.PersonInvolvedID = 1556 and
          not exists (select * 
                      from EventPerson788 
                      where EventPerson788.LinkedSuspectID = 1556) 
    union all
    select EventPerson788.sourceid
    from EventPerson788
    where EventPerson788.LinkedSuspectID = 1556
  ) as EventPerson  
  inner join BASE  
    on EventPerson.DUID=base.ID 
where
  BASE.IsActive=1

ここに画像の説明を入力

于 2013-08-11T13:47:58.643 に答える
1

さて、あなたは SQL Server にテーブルの行数をカウントするように要求しています。では、なぜEventPersonここでシークがスキャンよりも優れていると期待するのでしょうか?

の場合COUNT、SQL Server オプティマイザはほとんどの場合スキャンを使用します。つまり、行を数える必要があります。結局のところ、すべての行をカウントする必要があります。他の null 非許容列にインデックスが作成されていない場合は、クラスター化インデックス スキャンを実行します。

小さな null 非許容列 (たとえば、ID INTまたはそのようなもの) にインデックスがある場合は、代わりにそのインデックスでスキャンを実行する可能性があります (すべての行をカウントするために読み取るデータが少なくなります)。

しかし、一般的に:シークは1 つまたはいくつかの行を選択するのに最適ですが、(カウントのように)すべての行を処理する場合は最悪です。

AdventureWorksサンプル データベースを使用している場合は、この動作を簡単に確認できます。

次のように 120000 行を超えるテーブルでaCOUNT(*)を実行する場合:Sales.SalesOrderDetail

SELECT COUNT(*) FROM Sales.SalesOrderDetail

次に、インデックス スキャンIX_SalesOrderDetail_ProductIDを実行します。120000 を超えるエントリをシークしても無駄です。

ただし、次のように、より小さなデータ セットに対して同じ操作を行うと、次のようになります。

SELECT COUNT(*) FROM Sales.SalesOrderDetail
WHERE ProductID = 897

次に、それらすべてから 2 行を取得します。SQL Server は、同じインデックスに対してインデックス シークを使用します。

于 2013-08-11T13:13:07.133 に答える