web-dev-qa-db-ja.com

データベース:地理的位置データを照会するための最高のパフォーマンス?

MySQLデータベースがあります。データベースにホームを格納し、データベースに対して文字通りたった1つのクエリを実行しますですが、このクエリを超高速で実行する必要があります =、およびそれは、四角いボックスの地理緯度と経度内のすべての家を返すことです。

SELECT * FROM homes 
WHERE geolat BETWEEN ??? AND ???
AND geolng BETWEEN ??? AND ???

ジオロケーションボックス内のすべての家を最も速く表示するこのクエリを実行できるように、ジオデータを保存する最良の方法はどのようになりますか?

基本的に:

  • このクエリを最も速く実行するために最高のSQLステートメントを使用していますか?
  • ボックス化されたジオロケーション境界内の家の結果を最速でクエリするために、おそらくデータベースを使用していなくても、他の方法はありますか?

役立つ場合は、以下にデータベーステーブルスキーマを含めます。

CREATE TABLE IF NOT EXISTS `homes` (
  `home_id` int(10) unsigned NOT NULL auto_increment,
  `address` varchar(128) collate utf8_unicode_ci NOT NULL,
  `city` varchar(64) collate utf8_unicode_ci NOT NULL,
  `state` varchar(2) collate utf8_unicode_ci NOT NULL,
  `Zip` mediumint(8) unsigned NOT NULL,
  `price` mediumint(8) unsigned NOT NULL,
  `sqft` smallint(5) unsigned NOT NULL,
  `year_built` smallint(5) unsigned NOT NULL,
  `geolat` decimal(10,6) default NULL,
  `geolng` decimal(10,6) default NULL,
  PRIMARY KEY  (`home_id`),
  KEY `geolat` (`geolat`),
  KEY `geolng` (`geolng`),
) ENGINE=InnoDB  ;

[〜#〜] update [〜#〜]

空間的意志が地球の曲率を考慮していることは理解していますが、地理データを最速で返すことに最も興味があります。これらの空間データベースパッケージが何らかの形でデータをより速く返す場合を除き、空間拡張を推奨しないでください。ありがとう

更新2

以下の誰も本当に質問に答えていないことに注意してください。私は私が受けるかもしれないどんな援助も本当に楽しみにしています。前もって感謝します。

39
HankW

MySQLジオロケーションのパフォーマンスに関する良い論文があります こちら

[〜#〜] edit [〜#〜]これが固定半径を使用していることを確認してください。また、距離を計算するアルゴリズムが最も高度である(つまり、地球を「ドリル」する)ことを100%確信しているわけではありません。

重要なことは、適切な距離検索を行うために、行数のボールパーク制限をアルゴリズムが安価に提供することです。


アルゴリズムは、ソースポイントの周囲の正方形の候補を取得し、milesで距離を計算することで事前フィルタリングします。

これを事前に計算するか、ソースが示すようにストアドプロシージャを使用します。

# Pseudo code
# user_lon and user_lat are the source longitude and latitude
# radius is the radius where you want to search
lon_distance = radius / abs(cos(radians(user_lat))*69);
min_lon = user_lon - lon_distance;
max_lon = user_lon + lon_distance;
min_lat = user_lat - (radius / 69);
max_lat = user_lat + (radius / 69);
SELECT dest.*,
  3956 * 2 * ASIN(
    SQRT(
      POWER(
        SIN(
          (user_lat - dest.lat) * pi() / 180 / 2
        ), 2
      ) + COS(
        user_lat * pi() / 180
      ) * COS(
        dest.lat * pi() / 180
      ) * POWER(
        SIN(
          (user_lon - dest.lon) * pi() / 180 / 2
        ), 2
      )
    )
  ) as distance
FROM dest
WHERE 
  dest.lon between min_lon and max_lon AND
  dest.lat between min_lat and max_lat
HAVING distance < radius
ORDER BY distance
LIMIT 10
13
Igor Zevaka

私は同じ問題を抱えていて、3部構成のブログ投稿を書きました。これは、地理インデックスよりも高速でした。

イントロベンチマーク[〜#〜] sql [〜#〜]

5
Evert

パフォーマンスのために本当に必要な場合は、データのバウンディングボックスを定義し、事前計算バウンディングボックスを挿入時にオブジェクトにマップし、後でクエリに使用できます。

結果セットがかなり小さい場合でも、正確な結果を提供できるようにしながら、アプリケーションロジックで精度の修正を行うことができます(データベースよりも水平方向にスケーリングしやすい)。

Bret Slatkinの geobox.py をご覧ください。これには、アプローチの優れたドキュメントが含まれています。

近い将来、より複雑なクエリを実行する場合は、MySQLと比較してPostgreSQLと PostGIS をチェックアウトすることをお勧めします。

2
tosh

MySQL 5.7 mysqlはST_Distance_Sphere()やST_Contains()などのgeoindexを使用できるため、パフォーマンスが向上します。

1
Anak1

これは、丸め領域を作成するために使用したいくつかの成功の秘trickです。つまり、36.12345、-120.54321にある場所があり、それを0.5マイル(概算)のグリッドボックス内にある他の場所とグループ化する場合、その地域を36.12x-120.54と呼ぶことができます。同じ丸め領域を持つ他のすべての場所は、同じボックスに分類されます。

明らかに、それはあなたにきれいな半径を与えません。つまり、あなたが見ている場所が別のエッジよりもあるエッジに近い場合です。ただし、この種のセットアップでは、メインロケーションのボックスを囲む8つのボックスを計算するのは簡単です。機知に:

[36.13x-120.55][36.13x-120.54][36.13x-120.53]
[36.12x-120.55][36.12x-120.54][36.12x-120.53]
[36.11x-120.55][36.11x-120.54][36.11x-120.53]

一致する丸めラベルを使用してすべての場所を取得し、データベースからそれらを取得したら、距離計算を使用して、使用するものを決定できます。

1
Ben

実際に使用しているインデックスはBツリーインデックスであり、クエリでBETWEENキーワードをサポートしています。これは、オプティマイザーがインデックスを使用して「ボックス」内の家を見つけることができることを意味します。ただし、常にインデックスを使用するわけではありません。 「ヒット」が多すぎる範囲を指定すると、インデックスは使用されません。

1
Peter Lindqvist

現在のアプローチに固執すると、1つの変更を行う必要があります。geolatとgeolongを個別にインデックス付けするのではなく、複合インデックスを作成する必要があります。

KEY `geolat_geolng` (`geolat`, `geolng`),

現在、クエリは2つのインデックスのいずれかのみを利用しています。

1
Ben

非常に優れた代替手段は、MongoDBとその Geospatial Indexing

0
jalogar

家?おそらく1万個もありません。 STRTree のようなメモリ内インデックスを使用するだけです。

0
novalis

これはかなり高速に見えます。私の唯一の懸念は、インデックスを使用して緯度3マイル以内のすべての値を取得し、経度3マイル以内の値でそれらをフィルタリングすることです。基になるシステムの仕組みを理解している場合、テーブルごとに1つのINDEXしか使用できないため、latまたはlongのインデックスは価値がありません。

大量のデータがある場合、might高速化して1x1マイルごとに一意の論理IDを付与し、SELECTに追加の制限を加えます(area = "23234/34234" OR area = "23235/34234" OR ...)あなたのポイントの周りのすべての正方形に対して、データベースが緯度経度が長い場合は、はるかに少ない平方マイルのデータのみをフィルタリングします。

主キーが( 'geolat'、 'geolng')で、特定の地理位置情報にホームがある場合にhome_idを保持する列を持つ別のテーブル 'GeoLocations'を作成することを検討できます。これにより、オプティマイザーは、home_idのリストを求めてディスク上でソートされる一連の地理的位置を検索できるようになります。次に、「homes」テーブルとの結合を実行して、それらのhome_idに関する情報を見つけることができます。

CREATE TABLE IF NOT EXISTS `GeoLocations` (
`geolat` decimal(10,6) NOT NULL,
`geolng` decimal(10,6) NOT NULL,
`home_id` int(10) NULL
PRIMARY KEY  (`geolat`,`geolng`)
);

SELECT GL.home_id
FROM GeoLocations GL
INNER JOIN Homes H
 ON GL.home_id = H.home_id
WHERE GL.geolat between X and Y
 and GL.geolng between X and Y
0
Clayton Stewart