web-dev-qa-db-ja.com

実行プランでソート演算子を削除してSQLクエリを最適化する

SQLデータは大きく急速に成長しているため、インデックスを使用してクエリを最適化する方法を検討し始めました。 SSMSの実行プランでオプティマイザーがクエリを処理する方法を見て、ソート演算子が使用されていることに気付きました。ソートは時期尚早にインデックスを介して行われる可能性があるため、ソート演算子はクエリ内の不適切なデザインを示していると聞きました。だからここに私がやっていることに似たサンプルのテーブルとデータがあります:

IF OBJECT_ID('dbo.Store') IS NOT NULL DROP TABLE dbo.[Store]
GO

CREATE TABLE dbo.[Store]
(
    [StoreId] int NOT NULL IDENTITY (1, 1),
    [ParentStoreId] int NULL,
    [Type] int NULL,
    [Phone] char(10) NULL,
    PRIMARY KEY ([StoreId])
)

INSERT INTO dbo.[Store] ([ParentStoreId], [Type], [Phone]) VALUES (10, 0, '2223334444')
INSERT INTO dbo.[Store] ([ParentStoreId], [Type], [Phone]) VALUES (10, 0, '3334445555')
INSERT INTO dbo.[Store] ([ParentStoreId], [Type], [Phone]) VALUES (10, 1, '0001112222')
INSERT INTO dbo.[Store] ([ParentStoreId], [Type], [Phone]) VALUES (10, 1, '1112223333')
GO

以下にクエリの例を示します。

SELECT [Phone]
FROM [dbo].[Store]
WHERE [ParentStoreId] = 10
AND ([Type] = 0 OR [Type] = 1)
ORDER BY [Phone]

クエリを高速化するために、非クラスター化インデックスを作成します。

CREATE NONCLUSTERED INDEX IX_Store ON dbo.[Store]([ParentStoreId], [Type], [Phone])

IX_Storeインデックスを作成するには、簡単な述語から始めます

[ParentStoreId] = 10
AND ([Type] = 0 OR [Type] = 1)

次に、ORDER BYの[Phone]列を追加し、SELECT出力をカバーします

そのため、[Phone][ParentStoreId] AND [Type]の後にソートされるため、インデックスが作成される場合でも、オプティマイザーは引き続きソート演算子を使用します(インデックスのソートではありません)。インデックスから[Type]列を削除してクエリを実行すると:

SELECT [Phone]
FROM [dbo].[Store]
WHERE [ParentStoreId] = 10
--AND ([Type] = 0 OR [Type] = 1)
ORDER BY [Phone]

当然、[Phone][ParentStoreId]でソートされるため、ソート演算子はオプティマイザーでは使用されません。

質問は、クエリをカバーするインデックス([Type]述部を含む)を作成し、オプティマイザーにソートを使用させないようにする方法ですか?

編集:

私が使用しているテーブルには2000万行以上あります

22
jodev

最初に、ソートが実際にパフォーマンスのボトルネックであることを確認する必要があります。並べ替えの期間は、並べ替えられる要素の数に依存し、特定の親ストアのストアの数は少ない可能性があります。 (つまり、where句を適用した後にソート演算子が適用されると仮定しています)。

ソートはインデックスを使用して時期尚早に行われる可能性があるため、ソート演算子はクエリ内の不適切なデザインを示していると聞きました

それは過剰な一般化です。多くの場合、ソート演算子は簡単にインデックスに移動でき、結果セットの最初の数行のみがフェッチされる場合、データベースは一致するすべての行をフェッチする必要がないため、クエリコストを大幅に削減できます(そしてソートします)すべて)最初のレコードを見つけますが、結果セットの順序でレコードを読み取り、十分なレコードが見つかったら停止します。

あなたの場合、結果セット全体をフェッチしているように見えるので、ソートによって事態がさら​​に悪化することはほとんどありません(結果セットが巨大でない限り)。また、あなたの場合、便利なソートされたインデックスを作成するのは簡単ではないかもしれません。なぜならwhere句にはorが含まれているからです。

ソート演算子をまだ削除したい場合は、次を試してください:

SELECT [Phone]
FROM [dbo].[Store]
WHERE [ParentStoreId] = 10
AND [Type] in (0, 1)
ORDER BY [Phone]    

または、次のインデックスを試すことができます。

CREATE NONCLUSTERED INDEX IX_Store ON dbo.[Store]([ParentStoreId], [Phone], [Type])

クエリオプティマイザーにParentStoreIdのみでインデックス範囲スキャンを実行させてから、インデックス内の一致するすべての行をスキャンし、Typeが一致する場合に出力します。ただし、これはより多くのディスクI/Oを引き起こす可能性が高いため、クエリの速度を上げるのではなく、遅くします。

編集:最後の手段として、次を使用できます。

SELECT [Phone]
FROM [dbo].[Store]
WHERE [ParentStoreId] = 10
AND [Type] = 0
ORDER BY [Phone]

UNION ALL

SELECT [Phone]
FROM [dbo].[Store]
WHERE [ParentStoreId] = 10
AND [Type] = 1
ORDER BY [Phone]

CREATE NONCLUSTERED INDEX IX_Store ON dbo.[Store]([ParentStoreId], [Type], [Phone])

アプリケーションサーバーで2つのリストを並べ替えます。ここで、事前に並べ替えられたリストを(マージ並べ替えのように)マージして、完全な並べ替えを回避できます。しかし、それは実際にはミクロ最適化であり、ソート自体を桁違いに高速化しますが、ボトルネックはネットワークとディスクI/Oであると予想されるため、クエリの合計実行時間に大きな影響を与えることはほとんどありません。特に、インデックスがクラスタ化されていないため、ディスクが大量のランダムアクセスを行うという事実を考慮してください。

17
meriton