web-dev-qa-db-ja.com

地理位置情報プロセスのクエリを高速化するにはどうすればよいですか

10,301,390のGPSレコード、都市、国、IPアドレスブロックを含むテーブルがあります。緯度と経度を含むユーザーの現在地を持っています。私はこのクエリを作成しました:

_SELECT
  *, point(45.1013021, 46.3021011) <@> point(latitude, longitude) :: point AS distance
FROM
  locs
WHERE
  (
    point(45.1013021, 46.3021011) <@> point(latitude, longitude)
  ) < 10 -- radius
ORDER BY
  distance LIMIT 1;
_

このクエリは、必要なものを提供してくれましたが、遅いです。緯度と経度を指定して1つのレコードを取得するには、2〜3秒かかりました。

latitude列とlongitude列でBツリーインデックスを試してみましたが、Gist( point(latitude, longitude));も試してみましたが、それでもクエリが遅くなります。

このクエリを高速化するにはどうすればよいですか?

更新:

_ORDER BY_が原因で速度が低下しているようですが、最短距離を取得したいので、問題は残ります。

5
xangr

関数ll_to_earthの使用に基づいてGistインデックスの使用を検討できます。このインデックスは、高速の「近くの」検索を可能にします。

CREATE INDEX 
   ON locs USING Gist (ll_to_earth(lat, lng));

このインデックスを取得したら、別の方法でクエリを実行する必要があります。

(lat、lng)のペアをearthタイプに変換し、インデックス付けされた値(同じタイプ)と比較する必要があります。クエリには2つの条件が必要です。1つは「近似」結果用、もう1つは「正確」条件用です。最初のものは前のインデックスを使用することができます:

SELECT
    *
FROM
    locs
WHERE
    /* First condition allows to search for points at an approximate distance:
       a distance computed using a 'box', instead of a 'circumference'.
       This first condition will use the index.
       (45.1013021, 46.3021011) = (lat, lng) of search center. 
       25000 = search radius (in m)
    */
    earth_box(ll_to_earth(45.1013021, 46.3021011), 25000) @> ll_to_earth(lat, lng) 

    /* This second condition (which is slower) will "refine" 
       the previous search, to include only the points within the
       circumference.
    */
    AND earth_distance(ll_to_earth(45.1013021, 46.3021011), 
             ll_to_earth(lat, lng)) < 25000 ;

このコードを使用するには、2つの拡張機能が必要です(ほとんどのPostgreSQLディストリビューションに含まれています)。

CREATE EXTENSION IF NOT EXISTS cube ;
CREATE EXTENSION IF NOT EXISTS earthdistance;

これは彼らのためのドキュメントです:

  • キューブ 。 @>演算子の説明をご覧ください。このモジュールは次のモジュールで必要です。
  • EarthDistanceearth_boxearth_distanceに関する情報がここにあります。このモジュールは、地球が球形であることを前提としています。これは、大部分のアプリケーションにとって十分な近似です。

Free World Cities Database から取られた220万行で構成されるテーブルを使用したテストでは、前のクエリに対する次の回答が得られます(これは正確には同じではありません)。

"ru","andra-ata","Andra-Ata","24",,44.9509,46.3327
"ru","andratinskiy","Andratinskiy","24",,44.9509,46.3327
"ru","chernozemelskaya","Chernozemelskaya","24",,44.9821,46.0622
"ru","gayduk","Gayduk","24",,44.9578,46.5244
"ru","imeni beriya","Imeni Beriya","24",,45.0208,46.3906
"ru","imeni kirova","Imeni Kirova","24",,45.2836,46.4847
"ru","kumskiy","Kumskiy","24",,44.9821,46.0622
"ru","kumskoy","Kumskoy","24",,44.9821,46.0622
"ru","lopas","Lopas","17",,44.937,46.1833
"ru","pyatogo dekabrya","Pyatogo Dekabrya","24",,45.1858,46.1656
"ru","svetlyy erek","Svetlyy Erek","24",,45.0079,46.4408
"ru","ulan tuk","Ulan Tuk","24",,45.1542,46.1097

タイミングについて「桁違い」の考えを持つために:pgAdmin IIIは、この答えを得るのに22ミリ秒かかると私に言っています。 (「すぐに使える」パラメータを使用したPostgreSQL 9.6.1、Mac OS 10.12、Core i7、SSDを搭載したMac)

10
joanolo

PostGISによる代替回答

1,000万行を使用している場合。おそらく、ステップアップしてPostGISにアップグレードする必要があります。

  1. ポイントを地理タイプに変換します。 GPSから来た場合はとにかくSRID 4326にいると思います。これにはgeometery(point)::geographyを使用できます。または、lat/longに保存する場合はST_MakePointを使用できます
  2. (ST_Pointsの)新しいgeom列にインデックスを作成します。
  3. 次に、ST_DWithinを使用します。この関数willインデックスを使用します(作成する場合)。
  4. 次に、境界ボックス内のポイントのST_Distanceのみを計算します

これは、ST_DWithinのsigです。

boolean ST_DWithin(geometry g1, geometry g2, double precision distance_of_srid);
boolean ST_DWithin(geography gg1, geography gg2, double precision distance_meters);
boolean ST_DWithin(geography gg1, geography gg2, double precision distance_meters, boolean use_spheroid);

回転楕円体または球に沿った距離を測定できます。

SELECT geom, ST_Distance(geom, point)
WHERE ST_DWithin( geom, pointgiven, limit to check in km )
ORDER BY geom <=> point ASC
LIMIT 1;
3
Evan Carroll