私はまだデータベース管理に不慣れで、検索クエリを最適化しようとしています。
次のようなクエリがあり、場合によっては実行に5〜15秒かかり、CPU使用率が100%になっていました。
DECLARE @point geography;
SET @point = geography::STPointFromText('POINT(3.3109015 6.648294)', 4326);
SELECT TOP (1)
[Result].[PointId] AS [PointId],
[Result].[PointName] AS [PointName],
[Result].[LegendTypeId] AS [LegendTypeId],
[Result].[GeoPoint] AS [GeoPoint]
FROM (
SELECT
[Extent1].[GeoPoint].STDistance(@point) AS distance,
[Extent1].[PointId] AS [PointId],
[Extent1].[PointName] AS [PointName],
[Extent1].[LegendTypeId] AS [LegendTypeId],
[Extent1].[GeoPoint] AS [GeoPoint]
FROM [dbo].[GeographyPoint] AS [Extent1]
WHERE 18 = [Extent1].[LegendTypeId]
) AS [Result]
ORDER By [Result].distance ASC
このテーブルには、PKに1つのクラスター化インデックスがあり、geography
タイプの列に1つの空間インデックスがあります。
したがって、上記のクエリを実行すると、スキャン操作が実行されていました。
そのため、LegendTypeId
列に非クラスター化インデックスを作成しました。
CREATE NONCLUSTERED INDEX [GeographyPoint_LegendType_NonClustered] ON [dbo].[GeographyPoint]
(
[LegendTypeId] ASC
)
INCLUDE ( [PointId],
[PointName],
[GeoPoint])
WITH (PAD_INDEX = OFF,
STATISTICS_NORECOMPUTE = OFF,
SORT_IN_TEMPDB = OFF,
DROP_EXISTING = OFF,
ONLINE = OFF,
ALLOW_ROW_LOCKS = ON,
ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO
クエリを次のように変更しました:
DECLARE @point geography;
SET @point = geography::STPointFromText('POINT({0} {1})', 4326);
SELECT TOP (1)
[GeoPoint].STDistance(@point) AS distance,
[PointId],
[PointName],
[LegendTypeId],
[GeoPoint]
FROM [GeographyPoint]
WHERE 18 = [LegendTypeId]
ORDER By distance ASC
そして、SQL Serverはスキャンの代わりにシークを実行します。
私の見解では、クエリの効率が向上していますが、これを運用環境に展開しても、同じ結果が得られます(CPU使用率が高く、クエリの実行に平均10秒かかります)。
注:このテーブルでは、データの挿入、更新、削除は行われません。検索/読み取りのみです。
それは私が間違っていることですか?
どうすれば修正できますか?
[〜#〜]編集[〜#〜]
インデックスシークの詳細
編集2:
クエリを変更して、メソッド「Nearest Neighbor」をリンクから使用しました: https://msdn.Microsoft.com/en-us/library/ff929109.aspx 、これが結果です、このクエリも検索に3〜5秒かかります-2番目のクエリと同様です(ただし、本番環境ではテストされていません)。
空間インデックス設定:
CREATE SPATIAL INDEX [SPATIAL_Point] ON [dbo].[GeographyPoint]
(
[GeoPoint]
)USING GEOGRAPHY_GRID
WITH (GRIDS =(LEVEL_1 = MEDIUM,LEVEL_2 = MEDIUM,LEVEL_3 = MEDIUM,LEVEL_4 = MEDIUM),
CELLS_PER_OBJECT = 16, PAD_INDEX = OFF,
STATISTICS_NORECOMPUTE =
OFF, SORT_IN_TEMPDB = OFF,
DROP_EXISTING = OFF,
ONLINE = OFF,
ALLOW_ROW_LOCKS = ON,
ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO
EDIT 3@MickyTの指示に従い、[LegendTypeId]
のインデックスを削除し、次のクエリを実行しました:
DECLARE @point geography;
SET @point = geography::STPointFromText('POINT(3.3109 6.6482)', 4326);
SELECT TOP (1)
[PointId],
[PointName],
[LegendTypeId],
[GeoPoint]
FROM [GeographyPoint] WITH(INDEX(SPATIAL_Point))
WHERE
[GeoPoint].STDistance(@point) IS NOT NULL AND
18 = [LegendTypeId]
ORDER By [GeoPoint].STDistance(@point) ASC
OPTION(MAXDOP 1)
このクエリの統計は
次に、このクエリを再度実行しました。
DECLARE @point geography;
SET @point = geography::STPointFromText('POINT(3.3109 6.6482)', 4326);
SELECT TOP (1)
[GeoPoint].STDistance(@point) AS distance,
[PointId],
[PointName],
[LegendTypeId],
[GeoPoint]
FROM [GeographyPoint] --WITH(INDEX(SPATIAL_Point))
WHERE 18 = [LegendTypeId]
ORDER By distance ASC
このクエリの統計は
次の設定を使用して、いくつかのテストを実行しました。
CREATE TABLE GeographyPoint (
ID INTEGER IDENTITY(1,1) NOT NULL PRIMARY KEY,
GeoPoint GEOGRAPHY NOT NULL,
LegendTypeID INTEGER NOT NULL
);
INSERT INTO GeographyPoint (GeoPoint, LegendTypeID)
SELECT TOP 1000000
Geography::Point(Rand(CAST(NEWID() AS VARBINARY(MAX))) * 2,Rand(CAST(NEWID() AS VARBINARY(MAX))) * 2,4326),
CAST(Rand(CAST(NEWID() AS VARBINARY(MAX))) * 25 AS INTEGER)
FROM Tally;
CREATE INDEX GP_IDX1 ON GeographyPoint(LegendTypeID) INCLUDE (ID, GeoPoint);
CREATE SPATIAL INDEX GP_SIDX ON GeographyPoint(GeoPoint) USING GEOGRAPHY_AUTO_GRID;
これにより、2 x 2度の広がりを持つ1,000,000個のランダムポイントのテーブルが得られます。
いくつかの異なるオプションを試した後、私が得ることができる最高のパフォーマンスは、空間インデックスを使用することを強制することでした。これを達成する方法はいくつかありました。 LegendTypeIDのインデックスを削除するか、ヒントを使用します。
どちらが自分の状況に最適かを判断する必要があります。個人的には、インデックスヒントを使用したくないので、他のクエリで必要でない場合は、他のインデックスを削除します。
クエリは互いに積み重ねられます
DECLARE @point geography;
SET @point = geography::Point(1,1,4326);
/*
Clustered index scan (PK)
SQL Server Execution Times:
CPU time = 641 ms, elapsed time = 809 ms
*/
SELECT TOP (1)
[GeoPoint].STDistance(@point) AS distance,
[ID],
[LegendTypeId],
[GeoPoint]
FROM [GeographyPoint]
WHERE 18 = [LegendTypeId]
ORDER By distance ASC
OPTION(MAXDOP 1)
/*
Index Seek NonClustered (GP_IDX1)
SQL Server Execution Times:
CPU time = 2250 ms, elapsed time = 2806 ms
*/
SELECT TOP (1)
[GeoPoint].STDistance(@point) AS distance,
[ID],
[LegendTypeId],
[GeoPoint]
FROM [GeographyPoint]
WHERE [GeoPoint].STDistance(@point) IS NOT NULL AND
18 = [LegendTypeId]
ORDER By [GeoPoint].STDistance(@point) ASC
OPTION(MAXDOP 1)
/*
For the next 2 queries
Clustered Index Seek (Spatial)
SQL Server Execution Times:
CPU time = 15 ms, elapsed time = 11 ms
*/
SELECT TOP (1)
[GeoPoint].STDistance(@point) AS distance,
[ID],
[LegendTypeId],
[GeoPoint]
FROM [GeographyPoint] WITH(INDEX(GP_SIDX))
WHERE [GeoPoint].STDistance(@point) IS NOT NULL AND
18 = [LegendTypeId]
ORDER By [GeoPoint].STDistance(@point) ASC
OPTION(MAXDOP 1)
DROP INDEX GP_IDX1 ON [GeographyPoint]
SELECT TOP (1)
[GeoPoint].STDistance(@point) AS distance,
[ID],
[LegendTypeId],
[GeoPoint]
FROM [GeographyPoint]
WHERE [GeoPoint].STDistance(@point) IS NOT NULL AND
18 = [LegendTypeId]
ORDER By [GeoPoint].STDistance(@point) ASC
OPTION(MAXDOP 1)