web-dev-qa-db-ja.com

インデックスは実行を高速化せず、場合によってはクエリの速度を低下させます。なぜそうなのですか?

私は物事をスピードアップするためにインデックスを実験していたが、結合の場合、インデックスはクエリの実行時間を改善しておらず、場合によっては物事を遅くしている。

テストテーブルを作成してデータを入力するクエリは次のとおりです。

CREATE TABLE [dbo].[IndexTestTable](
    [id] [int] IDENTITY(1,1) PRIMARY KEY,
    [Name] [nvarchar](20) NULL,
    [val1] [bigint] NULL,
    [val2] [bigint] NULL)

DECLARE @counter INT;
SET @counter = 1;

WHILE @counter < 500000
BEGIN
    INSERT INTO IndexTestTable
      (
        -- id -- this column value is auto-generated
        NAME,
        val1,
        val2
      )
    VALUES
      (
        'Name' + CAST((@counter % 100) AS NVARCHAR),
        Rand() * 10000,
        Rand() * 20000
      );

    SET @counter = @counter + 1;
END

-- Index in question
CREATE NONCLUSTERED INDEX [IndexA] ON [dbo].[IndexTestTable]
(
    [Name] ASC
)
INCLUDE (   [id],
    [val1],
    [val2])

改善されたクエリ1(わずかですが、改善は一貫しています)は次のとおりです。

SELECT *
FROM   IndexTestTable I1
       JOIN IndexTestTable I2
            ON  I1.ID = I2.ID
WHERE  I1.Name = 'Name1'

インデックスなしの統計と実行プラン(この場合、テーブルはデフォルトのクラスター化インデックスを使用します):

(5000 row(s) affected)
Table 'IndexTestTable'. Scan count 2, logical reads 5580, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

(1 row(s) affected)

 SQL Server Execution Times:
   CPU time = 109 ms,  elapsed time = 294 ms.

enter image description here

今インデックスが有効になっています:

(5000 row(s) affected)
Table 'IndexTestTable'. Scan count 2, logical reads 2819, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

(1 row(s) affected)

 SQL Server Execution Times:
   CPU time = 94 ms,  elapsed time = 231 ms.

enter image description here

これで、インデックスが原因で速度が低下するクエリ(クエリはテスト用に作成されているため、無意味です):

SELECT I1.Name,
       SUM(I1.val1),
       SUM(I1.val2),
       MIN(I2.Name),
       SUM(I2.val1),
       SUM(I2.val2)
FROM   IndexTestTable I1
       JOIN IndexTestTable I2
            ON  I1.Name = I2.Name
WHERE   
       I2.Name = 'Name1'
GROUP BY
       I1.Name

クラスター化インデックスを有効にした場合:

(1 row(s) affected)
Table 'IndexTestTable'. Scan count 4, logical reads 60, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 1, logical reads 155106, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

(1 row(s) affected)

 SQL Server Execution Times:
   CPU time = 17207 ms,  elapsed time = 17337 ms.

enter image description here

今インデックスが無効になっています:

(1 row(s) affected)
Table 'IndexTestTable'. Scan count 5, logical reads 8642, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 2, logical reads 165212, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

(1 row(s) affected)

 SQL Server Execution Times:
   CPU time = 17691 ms,  elapsed time = 9073 ms.

enter image description here

質問は次のとおりです。

  1. インデックスはSQL Serverによって提案されていますが、なぜそれが大幅な違いによって速度を落とすのですか?
  2. ほとんどの時間を費やしている入れ子ループ結合とは何ですか?その実行時間を改善する方法は?
  3. 私が間違っている、または見逃していることはありますか?
  4. 既定のインデックス(主キーのみ)を使用すると、時間がかからなくなり、非クラスター化インデックスが存在する場合、結合テーブルの各行で、結合が名前列にあるため、結合されたテーブルの行をすばやく見つける必要があります。インデックスが作成されました。これはクエリ実行プランに反映されており、IndexAがアクティブな場合はインデックスシークのコストは低くなりますが、それでもなぜ遅いのでしょうか。また、スローダウンを引き起こしているネストループの左外部結合には何がありますか?

SQL Server 2012の使用

35
SpeedBirdNine

SQL Serverによってインデックスが提案されているにもかかわらず、大幅な違いによって速度が低下するのはなぜですか?

インデックスの提案は、クエリオプティマイザーによって行われます。既存のインデックスが適切に機能しないテーブルからの論理選択に遭遇した場合、可能性があります「欠落インデックス」の提案を出力に追加します。これらの提案は日和見的です。これらはクエリの完全な分析に基づくものではなく、より広い考慮事項を考慮していません。せいぜい、これらはより役立つインデックス作成が可能であり、熟練したDBAが検討する必要があることを示しています。

欠落しているインデックスの提案について他に言うことは、それらがオプティマイザの原価計算モデルに基づいており、オプティマイザは提案されたインデックスが推定値をどれだけ削減できるかによって推定するクエリのコスト。ここでのキーワードは「モデル」と「推定」です。クエリオプティマイザーは、ハードウェア構成やその他のシステム構成オプションについてほとんど何も知りません。そのモデルは、ほとんどの場合、ほとんどのシステムのほとんどの人に妥当な計画の結果をもたらす固定数に基づいています。使用された正確なコスト数の問題は別として、結果は常に見積もりであり、見積もりは間違っている可能性があります。

ほとんどの時間を費やしている入れ子ループ結合とは何ですか?その実行時間を改善する方法はありますか?

クロス結合操作自体のパフォーマンスを改善するために行うべきことはほとんどありません。ネストされたループは、クロス結合で可能な唯一の物理的な実装です。結合の内側のテーブルスプールは、各外側の行の内側の再スキャンを回避するための最適化です。これが有用なパフォーマンス最適化であるかどうかは、さまざまな要因に依存しますが、私のテストでは、クエリを使用しない方が良いでしょう。繰り返しますが、これはコストモデルを使用した結果です-私のCPUとメモリシステムは、おそらくあなたのシステムとは異なるパフォーマンス特性を持っています。テーブルスプールを回避するための特定のクエリヒントはありませんが、スプールの有無にかかわらず実行パフォーマンスをテストするために使用できる文書化されていないトレースフラグ(8690)があります。これが実際の本番システムの問題である場合、TF 8690を有効にして作成された計画に基づく計画ガイドを使用して、スプールのない計画を強制できます。ドキュメント化されていないトレースフラグを運用環境で使用することはお勧めしません。インストールが技術的にサポートされなくなり、トレースフラグが望ましくない副作用を引き起こす可能性があるためです。

何か間違ったことや見逃していることはありますか?

欠落している主なことは、非クラスター化インデックスを使用する計画では、オプティマイザーのモデルによると推定コストが低くなりますが、実行時間に重大な問題があることです。クラスタ化インデックスを使用して、プラン内のスレッド全体の行の分布を見ると、かなり良い分布が見られるでしょう。

Scan plan

Nonclustered Index Seekを使用する計画では、作業は1つのスレッドによって完全に実行されます。

Seek plan

これは、並列スキャン/シーク操作によって作業がスレッド間で分散される方法の結果です。並列スキャンがインデックスシークよりも作業を分散することが常に当てはまるわけではありませんが、この場合は分散されます。より複雑な計画には、スレッド間で作業を再分配するための交換の再パーティション化が含まれる場合があります。このプランにはそのような交換はないため、行がスレッドに割り当てられると、関連するすべての作業は同じスレッドで実行されます。実行プラン内の他のオペレーターの作業配分を見ると、すべての作業が、インデックスシークの場合と同じスレッドによって実行されていることがわかります。

スレッド間の行分散に影響を与えるクエリヒントはありません。重要なことは、可能性を認識し、実行計画の十分な詳細を読み取って、問題の原因を特定できるようにすることです。

デフォルトのインデックス(主キーのみ)を使用すると、時間がかからなくなり、非クラスタ化インデックスが存在するため、結合テーブルの各行で、結合テーブルの行がより早く見つかるはずです。なぜなら、結合はインデックスが作成されたName列にあるからです。これはクエリ実行プランに反映されており、IndexAがアクティブな場合はインデックスシークのコストは低くなりますが、それでもなぜ遅いのでしょうか。また、スローダウンを引き起こしているネストされたループの左外部結合には何がありますか?

これで、予想どおり、非クラスター化インデックスプランの方が効率的であることは明らかです。パフォーマンスの問題を説明するのは、実行時のスレッド間での作業の分散が悪いだけです。

例を完了し、これまでに説明したことのいくつかを説明するために、より良い作業分散を実現する1つの方法は、一時テーブルを使用して並列実行を実行することです。

SELECT
    val1,
    val2
INTO #Temp
FROM dbo.IndexTestTable AS ITT
WHERE Name = N'Name1';

SELECT 
    N'Name1',
    SUM(T.val1),
    SUM(T.val2),
    MIN(I2.Name),
    SUM(I2.val1),
    SUM(I2.val2)
FROM   #Temp AS T
CROSS JOIN IndexTestTable I2
WHERE
    I2.Name = 'Name1'
OPTION (FORCE ORDER, QUERYTRACEON 8690);

DROP TABLE #Temp;

これにより、より効率的なインデックスシークを使用し、テーブルスプールを備えておらず、作業をスレッド間で適切に分散するプランが作成されます。

Optimal plan

私のシステムでは、このプランはクラスター化インデックススキャンバージョンよりも大幅に高速に実行されます。

並列クエリ実行の内部について詳しく知りたい場合は、 私のPASS Summit 2013セッションの記録を見る をご覧ください。

24
Paul White 9

これは実際にはインデックスの問題ではなく、より不適切に記述されたクエリです。名前の一意の値は100個しかないため、名前ごとに一意のカウントは5000個になります。

したがって、表1の各行について、表2から5000を結合しています。25020004行と言えますか。

これを試してください、これはあなたがリストしたものだけの1つのインデックスであることに注意してください。

    DECLARE @Distincts INT
    SET @Distincts = (SELECT  TOP 1 COUNT(*) FROM IndexTestTable I1 WHERE I1.Name = 'Name1' GROUP BY I1.Name)
    SELECT I1.Name
    , @Distincts
    , SUM(I1.val1) * @Distincts
    , SUM(I1.val2) * @Distincts
    , MIN(I2.Name)
    , SUM(I2.val1)
    , SUM(I2.val2)
    FROM   IndexTestTable I1
    LEFT OUTER JOIN

    (
        SELECT I2.Name
        , SUM(I2.val1) val1
        , SUM(I2.val2) val2
        FROM IndexTestTable I2
        GROUP BY I2.Name
    ) I2 ON  I1.Name = I2.Name
    WHERE I1.Name = 'Name1'
    GROUP BY  I1.Name

そして時間:

    SQL Server parse and compile time: 
       CPU time = 0 ms, elapsed time = 8 ms.
    Table 'IndexTestTable'. Scan count 1, logical reads 31, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

     SQL Server Execution Times:
       CPU time = 0 ms,  elapsed time = 1 ms.

    (1 row(s) affected)
    Table 'IndexTestTable'. Scan count 2, logical reads 62, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
    Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

     SQL Server Execution Times:
       CPU time = 16 ms,  elapsed time = 10 ms.

enter image description here

不正な形式のクエリのSQLインデックスを非難することはできません

0
Adrian Sullivan