web-dev-qa-db-ja.com

OR条件を使用すると、個別のSELECTと比較して、インデックスのシークがはるかに遅くなる

これらの質問と与えられた回答に基づいて:

SQL 2008 Server-非常に大きなテーブルに接続されているとパフォーマンスが低下する可能性があります

履歴データを含む大きなテーブルがSQL Server 2008標準メモリを割り当てすぎます-他のデータベースのパフォーマンスが低下します

データベースSupervisionPに次のように定義されたテーブルがあります。

CREATE TABLE [dbo].[PenData](
    [IDUkazatel] [smallint] NOT NULL,
    [Cas] [datetime2](0) NOT NULL,
    [Hodnota] [real] NULL,
    [HodnotaMax] [real] NULL,
    [HodnotaMin] [real] NULL,
 CONSTRAINT [PK_Data] PRIMARY KEY CLUSTERED 
(
    [IDUkazatel] ASC,
    [Cas] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

ALTER TABLE [dbo].[PenData]  WITH NOCHECK ADD  CONSTRAINT [FK_Data_Ukazatel] FOREIGN KEY([IDUkazatel])
REFERENCES [dbo].[Ukazatel] ([IDUkazatel])

ALTER TABLE [dbo].[PenData] CHECK CONSTRAINT [FK_Data_Ukazatel]

これにはcca 211ミリオンの行が含まれます。

私は次のステートメントを実行します:

DECLARE @t1 DATETIME;
DECLARE @t2 DATETIME;

SET @t1 = GETDATE();
SELECT min(cas) from PenData p WHERE IDUkazatel=24
SELECT min(cas) from PenData p WHERE IDUkazatel=25
SET @t2 = GETDATE();
SELECT DATEDIFF(millisecond,@t1,@t2) AS elapsed_ms;


SET @t1 = GETDATE();
SELECT min(cas) from PenData p WHERE IDUkazatel=24 OR IDUkazatel=25 
SET @t2 = GETDATE();
SELECT DATEDIFF(millisecond,@t1,@t2) AS elapsed_ms;

結果は次のとおりです。

Execution plan

3番目のSELECTは、SQL Serverメモリキャッシュにさらに多くのデータをロードします。

3番目のSELECTが最初の2つのSELECT(16ミリ秒)よりもはるかに遅い(8.5秒)のはなぜですか? ORを使用して3番目の選択のパフォーマンスを向上させるにはどうすればよいですか?次のSQLコマンドを実行したいのですが、この場合、単一の選択よりもカーソルの作成と個別のクエリの実行の方がはるかに速いようです。

 SELECT MIN(cas) from PenData p WHERE IDUkazatel IN (SELECT IDUkazatel FROM  ...)

[〜#〜]編集[〜#〜]

デビッドが示唆したように、私は太い矢印の上にカーソルを合わせました:

FatArrow

8
Vojtěch Dohnal

最初の2つのクエリでは、クラスタードインデックスをスキャンしてIDUkazatelのその値の最初のエントリをスキャンするだけです。インデックスの順序により、その行はIDUkazatelのその値のcasの最小値になるためです。

2番目のクエリでは、この最適化は値ではなく、おそらく_IDUkazatel=24_の最初の行を検索してから、_IDUkazatel=25_で最後の行までインデックスをスキャンして、それらすべての行でcasの最小値を見つけます。

その太い矢印の上にカーソルを合わせると、多くの行(確かに24のすべての行、おそらく25の行もすべて)が読み取られているのがわかりますが、他の2つの計画の出力の細い矢印は、topアクションを示しています。 1行のみを考慮します。

各クエリを実行してから、見つかった最小値の最小値を取得できます。

_SELECT MIN(cas)
FROM   (
        SELECT cas=MIN(cas) FROM PenData p WHERE p.IDUkazatel = 24
        UNION ALL
        SELECT cas=MIN(cas) FROM PenData p WHERE p.IDUkazatel = 25
    ) AS minimums
_

つまり、明示的なIDUkazatel句ではなく、OR値を含むテーブルがあるようです。以下のコードはその配置で機能し、テーブル名_@T_をIDUkazatel値を含むテーブルの名前に置き換えるだけです。

_SELECT 
    MinCas = MIN(CA.PartialMinimum)
FROM @T AS T
CROSS APPLY 
(
    SELECT 
        PartialMinimum = MIN(PD.Cas)
    FROM dbo.PenData AS PD
    WHERE 
        PD.IDUkazatel = T.IDUkazatel
) AS CA;
_

理想的な世界では、SQL Serverクエリオプティマイザーがこの書き換えを実行しますが、現在このオプションが常に考慮されているとは限りません。

11
David Spillett