web-dev-qa-db-ja.com

順序付けされた列にインデックスが付けられているにもかかわらず、SQL ServerのORDERBYが遅いのはなぜですか?

大まかに次のようなSQLクエリ(LINQ to Entitiesによって生成された)があります。

SELECT * FROM [mydb].[dbo].[employees]
JOIN [mydb].[dbo].[industry]
  ON jobs.industryId = industry.id
JOIN [mydb].[dbo].[state]
  ON jobs.stateId = state.id
JOIN [mydb].[dbo].[positionType]
  ON jobs.positionTypeId = positionType.id
JOIN [mydb].[dbo].[payPer]
  ON jobs.salaryPerId = payPer.id
JOIN [mydb].[dbo].[country]
  ON jobs.countryId = country.id
WHERE countryName = 'US'
ORDER BY startDatetime

クエリは約1200行を返しますが、これは膨大な量ではないと思います。残念ながら、16秒もかかります。 ORDER BYがない場合、クエリの所要時間は1秒未満です。

SQL Server Management Studioを使用して、startDatetime列にインデックスを作成し、「cityId、industryId、startDatetime、positionTypeId、payPerId、stateId」(つまり、で使用する「ジョブ」のすべての列)にクラスター化インデックスを作成しました。 JOINと、列でORDER BYを使用します)。 JOINで使用する各列には、すでに個別のインデックスがあります。残念ながら、これによってクエリが高速化されることはありません。

私はショープランを実行し、次のようになりました。

   |--Nested Loops(Inner Join, OUTER REFERENCES:([mydb].[dbo].[jobs].[cityId]))
       |--Nested Loops(Inner Join, OUTER REFERENCES:([mydb].[dbo].[jobs].[stateId]))
       |    |--Nested Loops(Inner Join, OUTER REFERENCES:([mydb].[dbo].[jobs].[industryId]))
       |    |    |--Nested Loops(Inner Join, OUTER REFERENCES:([mydb].[dbo].[jobs].[positionTypeId]))
       |    |    |    |--Nested Loops(Inner Join, OUTER REFERENCES:([mydb].[dbo].[jobs].[salaryPerId]))
       |    |    |    |    |--Sort(ORDER BY:([mydb].[dbo].[jobs].[issueDatetime] ASC))
       |    |    |    |    |    |--Hash Match(Inner Join, HASH:([mydb].[dbo].[currency].[id])=([mydb].[dbo].[jobs].[salaryCurrencyId]))
       |    |    |    |    |         |--Index Scan(OBJECT:([mydb].[dbo].[currency].[IX_currency]))
       |    |    |    |    |         |--Nested Loops(Inner Join, WHERE:([mydb].[dbo].[jobs].[countryId]=[mydb].[dbo].[country].[id]))
       |    |    |    |    |              |--Index Seek(OBJECT:([mydb].[dbo].[country].[IX_country]), SEEK:([mydb].[dbo].[country].[countryName]='US') ORDERED FORWARD)
       |    |    |    |    |              |--Clustered Index Scan(OBJECT:([mydb].[dbo].[jobs].[PK_jobs]))
       |    |    |    |    |--Clustered Index Seek(OBJECT:([mydb].[dbo].[payPer].[PK_payPer]), SEEK:([mydb].[dbo].[payPer].[id]=[mydb].[dbo].[jobs].[salaryPerId]) ORDERED FORWARD)
       |    |    |    |--Clustered Index Seek(OBJECT:([mydb].[dbo].[positionType].[PK_positionType]), SEEK:([mydb].[dbo].[positionType].[id]=[mydb].[dbo].[jobs].[positionTypeId]) ORDERED FORWARD)
       |    |    |--Clustered Index Seek(OBJECT:([mydb].[dbo].[industry].[PK_industry]), SEEK:([mydb].[dbo].[industry].[id]=[mydb].[dbo].[jobs].[industryId]) ORDERED FORWARD)
       |    |--Clustered Index Seek(OBJECT:([mydb].[dbo].[state].[PK_state]), SEEK:([mydb].[dbo].[state].[id]=[mydb].[dbo].[jobs].[stateId]) ORDERED FORWARD)
       |--Clustered Index Seek(OBJECT:([mydb].[dbo].[city].[PK_city]), SEEK:([mydb].[dbo].[city].[id]=[mydb].[dbo].[jobs].[cityId]) ORDERED FORWARD)

重要な行は「| --Sort(ORDER BY:([mydb]。[dbo]。[jobs]。[issueDatetime] ASC))」のようです—その列のインデックスについては何も言及されていません。

ORDER BYによってクエリが非常に遅くなるのはなぜですか。また、クエリを高速化するにはどうすればよいですか。

17
George

クエリにorderが含まれていない場合は、見つかったすべてのorederのデータが返されます。クエリを再度実行したときに、データが同じ順序で返されるという保証はありません。

Order by句を含めると、dabataseは正しい順序で行のリストを作成し、その順序でデータを返す必要があります。これには多くの余分な処理が必要になる可能性があり、これは余分な時間につながります。

クエリが返す可能性のある多数の列を並べ替えるには、おそらく時間がかかります。ある時点でバッファスペースが不足し、データベースがスワッピングを開始する必要があり、パフォーマンスが低下します。

より少ない列を返すようにして(Select *の代わりに必要な列を指定して)、クエリがより速く実行されるかどうかを確認してください。

13
Scott Bruns

クエリはすべての列を投影するため(*)、結合条件に5つの列が必要であり、結合されたテーブル列である可能性が高いものに非選択的なWHERE句があり、 インデックスの転換点 :にヒットします。オプティマイザーは、テーブル全体をスキャンし、フィルター処理して、インデックスを範囲スキャンするように並べ替えてから、テーブル内の各キーを検索して、必要な追加の列(結合の場合は5、残りの列)を取得する方がコストが低いと判断しますのために *)。

このクエリを部分的にカバーするためのより良いインデックスは次のとおりです。

CREATE INDEX ... ON .. (countryId, startDatetime);

クラスター化インデックスを作成するというJeffreyの提案は、クエリを100%カバーし、パフォーマンスを確実に向上させますが、クラスター化インデックスを変更すると、多くの副作用が発生します。上記のように、非クラスター化インデックスから始めます。他のクエリで必要とされない限り、作成した他のすべての非クラスター化インデックスを削除できます。これらはこのクエリに役立ちません。

7
Remus Rusanu

以下のコードも試してみてください

レコードを一時テーブルに挿入しますOrderby句を使用しません

SELECT * into #temp FROM [mydb].[dbo].[employees]
JOIN [mydb].[dbo].[industry]
  ON jobs.industryId = industry.id
JOIN [mydb].[dbo].[state]
  ON jobs.stateId = state.id
JOIN [mydb].[dbo].[positionType]
  ON jobs.positionTypeId = positionType.id
JOIN [mydb].[dbo].[payPer]
  ON jobs.salaryPerId = payPer.id
JOIN [mydb].[dbo].[country]
  ON jobs.countryId = country.id
WHERE countryName = 'US'

OrderBy句を使用してステートメントを実行します

Select * from #temp ORDER BY startDatetime
3
Pankaj

ニュース速報:列にインデックスを付けることは、並べ替えを高速化するのに役立ちません。

クエリを非常に高速にしたい場合は、テーブルの順序を逆にします。具体的には、結合されたテーブルの最初にテーブルcountryをリストします。理由? where句は、これらすべての結合を行う代わりに、最初のテーブルから行をフィルタリングできます。then行をフィルタリングします。

2
Bohemian

クラスター化インデックスのフィールドはどのような順序で含まれていますか? startDateTimeフィールドを最初に配置して、ORDER BYそれに一致するか、この場合は(countryId, startDateTime)単一のcountryIdを(間接的にはcountryName経由で)選択し、次にstartDateTimeで並べ替えたいので、この順序で前もって行います。

2
Jeffrey Hantin