現在、クエリの最適化についていくつかのことを学んでいて、私はさまざまなクエリを試していて、この「問題」に遭遇しました。
私は、AdventureWorks2014データベースを使用しています。この簡単なクエリを実行しました。
テーブル構造( https://www.sqldatadictionary.com/AdventureWorks2014.pdf から取得):
_SELECT C.CustomerID
FROM Sales.Customer AS C
WHERE C.CustomerID > 100
_
19,720行を返します
Sales.Customerの行の総数= 19,820
そして、CustomerIDが実際にテーブルのPKであるだけでなく、クラスター化インデックスも持っていることを確認した後(ただし、非クラスター化インデックスを使用している場合)、実際にそうです。
_EXEC SP_HELPINDEX 'Sales.Customer'
_
実施計画はこちら↓
https://www.brentozar.com/pastetheplan/?id=B1g1SihGr
私は、大量のデータに直面したり、データセットの50%以上を返したりした場合、クエリオプティマイザーはインデックススキャンを優先することを読んだことがあります。しかし、そのテーブルは全体としてわずか20,000行(正確には19,820行)であり、決して大きなテーブルではありません。
このクエリを実行すると:
_SELECT C.CustomerID
FROM Sales.Customer AS C
WHERE C.CustomerID > 30000
_
118行を返します
https://www.brentozar.com/pastetheplan/?id=Byyux32MS
代わりにインデックスシークを取得するので、「50%以上のケース」が原因であると考えましたが、次のクエリも実行しました。
_SELECT C.CustomerID
FROM Sales.Customer AS C
WHERE C.CustomerID > 20000
_
10,118行を返します
https://www.brentozar.com/pastetheplan/?id=HJ9oV33zr
また、データセットの50%以上を返すものの、インデックスシークも使用しました。
ここで何が起こっているのでしょうか?
編集:
IO統計がオンの場合、> 100クエリは次を返します:
_Table 'Customer'. Scan count 1, logical reads 37, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
_
> 20,000が返された間:
_Table 'Customer'. Scan count 1, logical reads 65, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
_
そこで、2万以上のWITH(FORCESCAN)
オプションを追加して、何が起こるかを確認します。
_Table 'Customer'. Scan count 1, logical reads 37, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
_
そのため、クエリオプティマイザがこの特定のクエリに対してインデックスシークを実行することを選択した場合でも、インデックススキャン(より少ない論理読み取り)を使用すると、実行が向上します。
不等号述語を使用するので、「シーク」操作は実際には(「最初」からではなく)ある値から始まり、クラスター化インデックスリーフレベルの最後に移動するスキャンになります。
一方、クラスター化されたインデックスキーである1つの列のみを返すため、インデックスを使用してもキーのルックアップ操作は行われません。オプティマイザーは、非クラスター化インデックスのスキャン(リーフレベルの2つのint列)またはクラスター化インデックスの部分スキャン(リーフレベルのすべての列)のどちらが安くなるかを推定する必要があります。
現在の統計(行数)およびメタデータ(1行のサイズとは何か)に基づいて推定します。オプティマイザが>20,000
述語。
大量のデータに直面したり、データセットの50%以上を返したりする場合、クエリオプティマイザーはインデックススキャンを優先します。
これは、オプティマイザがクラスタ化インデックスまたはテーブルスキャンと非クラスタ化インデックスシーク+キールックアップのどちらを実行するかを選択する必要がある場合の事実です。
あなたの場合、CustomerID
のインデックスが非クラスター化されていると、常にそのインデックスでシーク操作が表示されますが、出力に別の列を追加すると、短い結果セットとテーブルでインデックスシーク+ RIDルックアップが表示されます大きなものをスキャンします。
コストベースの最適化では、オプティマイザーは所定の時間に可能な限り最良の実行を見つけます。
この表の各インデックスのインデックスサイズを確認すると、
SELECT
i.name AS IndexName,
SUM(page_count * 8) AS IndexSizeKB
FROM sys.dm_db_index_physical_stats(
db_id(), object_id('Sales.Customer'), NULL, NULL, 'DETAILED') AS s
JOIN sys.indexes AS i
ON s.object_id = i.object_id AND s.index_id = i.index_id
GROUP BY i.name
ORDER BY i.name;
IX_Customer_TerritoryID || 288
PK_Customer_CustomerID || 976
したがって、IX_Customer_TerritoryID
のインデックスサイズはPK_Customer_CustomerID
よりはるかに小さいことがわかります。
両方のクエリのコストを比較し、
SELECT C.CustomerID
FROM Sales.Customer AS C
WHERE C.CustomerID > 100
SELECT C.CustomerID
FROM Sales.Customer AS C WITH(INDEX(PK_Customer_CustomerID))
WHERE C.CustomerID > 100
インデックスI/O cost
のクエリのIX_Customer_TerritoryID
は、PK_Customer_CustomerID
のクエリよりも小さくなっています。