web-dev-qa-db-ja.com

なぜインデックスシークをしているのか

以下のようにサンプルテーブルを作成しました

CREATE TABLE [dbo].[StatisticsDemo](
    [ID] [int] IDENTITY(1,1) NOT NULL,
    [Name] [nvarchar](50) NULL
   ) ON [PRIMARY]

次に、以下のデータを挿入しました:

SELECT NAME,COUNT(*) AS COUNT 
FROM StatisticsDemo 
GROUP BY NAME

NAME    COUNT
-------------
AABBCC  59999
XXYYZZ  1

次に、以下の非クラスター化インデックスを作成しました:

CREATE NONCLUSTERED INDEX [NCI_STATISTICSDEMO_NAME] ON [dbo].[StatisticsDemo]
(
    [Name] ASC
)

今、私は以下のクエリを実行しました:

SELECT NAME FROM [dbo].[StatisticsDemo] 
WHERE NAME = 'AABBCC'

予想どおり、59999行を返しますが、非クラスター化インデックスに対してインデックスシークを実行しています。しかし、私の知る限りでは、データの99.99%が選択クエリで言及されているフィルター基準を満たしているため、インデックススキャンを実行する必要があります。

インデックススキャンではなくインデックスシークを行う理由を教えてください。

全体のアクティビティの目的は、SQL Serverが統計を調べて、実行計画を準備する前に、クエリのフィルター基準に一致するレコードの数を特定し、レコードがテーブル内の合計レコードのうち一致する場合、SCANまたはSEEKを実行することを決定します。一致するレコードの%がテーブル内のレコードの総数とほぼ等しい場合、SCANを実行する必要があります。しかし、それは起こっていません。 AdventureWorks2016データベースを使用していて、クエリの下で実行している場合も同じです。

select * from [Sales].[SalesOrderHeader] WHERE SalesOrderID >= 43659 AND 
SalesOrderID <= 73659

上記のクエリは31465から30001レコードを返します。しかし、それはまだクラスター化インデックスシークを実行しています。

私はひどく混乱していて、それは私の概念を揺さぶっています。 :(いくつかは助けてください。

PS:プランのキャッシュも削除しましたが、運はありませんでした。 SQL Serverのバージョンは2016です。

全体のアクティビティの目的は、SQL Serverが統計を調べて、実行計画を準備する前に、クエリのフィルター基準に一致するレコードの数を特定し、レコードがテーブル内の合計レコードのうち一致する場合、SCANまたはSEEKを実行することを決定します。一致するレコードの%がテーブル内のレコードの総数とほぼ等しい場合、SCANを実行する必要があります。

これは正しくないので、表示されない理由を説明します。 BETWEEN 43659 AND 73659範囲シークは部分スキャンを実行しています。 Bツリーを使用して、スキャンを開始するポイントを探すことができるため(43659未満の読み取りは避けてください)、73659より大きい値の行がある場合は、早期に終了する可能性があります。 。

範囲内のareである行については、インデックス順スキャンとまったく同じ方法で、ページを読み取り、リンクされたリストを次のリーフページまでたどります。

ここでスキャンする必要はありません。せいぜい、ルートからリーフまでナビゲートして開始点を見つけるための論理的な読み取りをいくつか節約できますが、求められる範囲外の追加の行を読み取ることを犠牲にします。

6
Martin Smith

あなたの理解は正しくなく、あなたのケースを 転換点 問題と混同していると思います。これは、よく読んで十分に理解する価値のある問題です。

SQL Serverでは、シークはBツリーを使用して特定の値で行の読み取りを開始または停止する操作です。理論的には、スキャンと同じ順序ですべての行をシーク読み取りすることができます。

Oracleおよびおそらく他のRDMSの場合、この操作(定義された開始値と停止値で複数の行を読み取る)は 範囲スキャンと呼ばれます。

これは、すべての行を読み取るシークの簡単なデモです

DROP TABLE IF EXISTS dbo.SeekTest
GO

CREATE TABLE dbo.SeekTest(
ID int PRIMARY KEY CLUSTERED,
filler char(8000) --to get 1 row per page
)

INSERT dbo.SeekTest (ID)
VALUES (1),(2),(3),(4),(5)
GO

SET STATISTICS IO ON

--this one scans
SELECT *
FROM dbo.SeekTest

--this one seeks
SELECT *
FROM dbo.SeekTest
WHERE ID > 0

シークは複数の行を読み取ることができることに注意してください。このデモでは、スキャンとまったく同じように読み取ります。

5
Forrest