大まかに次のような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によってクエリが非常に遅くなるのはなぜですか。また、クエリを高速化するにはどうすればよいですか。
クエリにorderが含まれていない場合は、見つかったすべてのorederのデータが返されます。クエリを再度実行したときに、データが同じ順序で返されるという保証はありません。
Order by句を含めると、dabataseは正しい順序で行のリストを作成し、その順序でデータを返す必要があります。これには多くの余分な処理が必要になる可能性があり、これは余分な時間につながります。
クエリが返す可能性のある多数の列を並べ替えるには、おそらく時間がかかります。ある時点でバッファスペースが不足し、データベースがスワッピングを開始する必要があり、パフォーマンスが低下します。
より少ない列を返すようにして(Select *の代わりに必要な列を指定して)、クエリがより速く実行されるかどうかを確認してください。
クエリはすべての列を投影するため(*
)、結合条件に5つの列が必要であり、結合されたテーブル列である可能性が高いものに非選択的なWHERE
句があり、 インデックスの転換点 :にヒットします。オプティマイザーは、テーブル全体をスキャンし、フィルター処理して、インデックスを範囲スキャンするように並べ替えてから、テーブル内の各キーを検索して、必要な追加の列(結合の場合は5、残りの列)を取得する方がコストが低いと判断しますのために *
)。
このクエリを部分的にカバーするためのより良いインデックスは次のとおりです。
CREATE INDEX ... ON .. (countryId, startDatetime);
クラスター化インデックスを作成するというJeffreyの提案は、クエリを100%カバーし、パフォーマンスを確実に向上させますが、クラスター化インデックスを変更すると、多くの副作用が発生します。上記のように、非クラスター化インデックスから始めます。他のクエリで必要とされない限り、作成した他のすべての非クラスター化インデックスを削除できます。これらはこのクエリに役立ちません。
以下のコードも試してみてください
レコードを一時テーブルに挿入します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
ニュース速報:列にインデックスを付けることは、並べ替えを高速化するのに役立ちません。
クエリを非常に高速にしたい場合は、テーブルの順序を逆にします。具体的には、結合されたテーブルの最初にテーブルcountry
をリストします。理由? where句は、これらすべての結合を行う代わりに、最初のテーブルから行をフィルタリングできます。then行をフィルタリングします。
クラスター化インデックスのフィールドはどのような順序で含まれていますか? startDateTime
フィールドを最初に配置して、ORDER BY
それに一致するか、この場合は(countryId, startDateTime)
単一のcountryId
を(間接的にはcountryName
経由で)選択し、次にstartDateTime
で並べ替えたいので、この順序で前もって行います。