9

私の Web サイトでの検索用に mysql select ステートメントを使用していますが、サイトが非常にビジー状態になるとパフォーマンスの問題が発生します。以下のクエリは、指定された緯度と経度から 25 マイル以内の 10 万レコードを超えるテーブルから広告を検索し、距離で並べ替えます。マイル数は、ユーザーが選択するため、異なる場合があります。

問題は、緯度と経度の 25 マイル以内にあるレコードではなく、テーブル内のすべてのレコードの計算を行うため、遅いと思うことです。where 句が 25 マイル以内の広告のみを選択するように、このクエリを修正することは可能ですか? 境界ボックスと空間インデックスについて読みましたが、それらをこのクエリに適用する方法がわかりません。緯度と経度の半径 25 マイルのレコードを選択する where 句を追加する必要がありますか?

SELECT 
    adverts.*, 
    round(sqrt((((adverts.latitude - '53.410778') * (adverts.latitude - '53.410778')) * 69.1 * 69.1) + ((adverts.longitude - '-2.97784') * (adverts.longitude - '-2.97784') * 53 * 53)), 1) as distance
FROM 
    adverts
WHERE 
    (adverts.type_id = '3')
HAVING 
    DISTANCE < 25
ORDER BY 
    distance ASC 
LIMIT 120,10

編集:テーブルスキーマを含めるように更新されました。テーブルはより複雑であり、クエリも複雑であることに注意してください。ただし、この問題に必要のないものは削除しました。

CREATE TABLE `adverts` (
`advert_id` int(10) NOT NULL AUTO_INCREMENT,
`type_id` tinyint(1) NOT NULL,
`headline` varchar(50) NOT NULL,
`description` text NOT NULL,
`price` int(4) NOT NULL,
`postcode` varchar(7) NOT NULL,
`latitude` float NOT NULL,
`longitude` float NOT NULL,
PRIMARY KEY (`advert_id`),
KEY `latlon` (`latitude`,`longitude`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8

mysqlステートメントでexplainを実行すると、行数が67900に設定されます。これは、半径25マイルよりもはるかに多く、追加は「場所を使用して;ファイルソートを使用して」に設定されています。

クエリには 0.3 秒かかりますが、これは非常に遅く、特に Web サイトが 1 秒あたりに多くのリクエストを受け取る場合に顕著です。

4

2 に答える 2

8

これを行う最も速い方法は、MySQLの地理空間拡張機能を使用することです。これは、すでにMyISAMテーブルを使用しているので十分簡単です。これらの拡張機能のドキュメントはここにあります:http://dev.mysql.com/doc/refman/5.6/en/spatial-extensions.html

POINTデータ型の新しい列を追加します。

ALTER TABLE `adverts` 
ADD COLUMN `geopoint` POINT NOT NULL AFTER `longitude`
ADD SPATIAL KEY `geopoint` (`geopoint`)

次に、既存の緯度と経度のフィールドからこの列にデータを入力できます。

UPDATE `adverts` 
SET `geopoint` = GeomFromText(CONCAT('POINT(',`latitude`,' ',`longitude`,')'));

WHERE次のステップは、句でCONTAINS制約として使用される入力緯度と経度に基づいてバウンディングボックスを作成することです。POINT目的の検索領域と指定された開始点に基づいて、要件に対応するX、Y座標のセットを決定する必要があります。

最後のクエリではPOINT、検索内にあるすべてのデータが検索されます。POLYGONその後、距離計算を使用して、データをさらに絞り込み、並べ替えることができます。

SELECT a.*, 
    ROUND( SQRT( ( ( (adverts.latitude - '53.410778') * (adverts.latitude - '53.410778') ) * 69.1 * 69.1 ) + ( (adverts.longitude - '-2.97784') * (adverts.longitude - '-2.97784') * 53 * 53 ) ), 1 ) AS distance
FROM adverts a
WHERE a.type_id = 3
AND CONTAINS(a.geopoint, GeomFromText('Polygon((0 0,0 3,3 3,3 0,0 0))'))
HAVING distance < 25
ORDER BY distance DESC
LIMIT 0, 30

GeomFromText('Polygon((0 0,0 3,3 3,3 0,0 0))')上記のは機能しないことに注意してください。検索開始の周囲の有効なポイントで座標を置き換える必要があります。緯度/経度の変更が予想される場合は、トリガーを使用してPOINTデータと関連付けSPATIAL KEYを最新の状態に保つことを検討する必要があります。HAVING大規模なデータセットでは、すべてのレコードの距離を計算し、句を使用してフィルタリングするよりも、パフォーマンスが大幅に向上するはずです。距離の決定と境界の作成に使用する関数を個人的に定義しましたPOLYGON

于 2012-09-22T04:11:37.877 に答える
6

クエリを高速化する方法はいくつかありますが、個人的にはこのPOW機能を利用します。

XをY乗した値を返します。

手動で乗算すると、同じ結果が得られますが、大きなテーブルでクエリが遅くなります。

SELECT a .* , 
    round( sqrt( 
        (POW( a.latitude -'53.410778', 2)* 68.1 * 68.1) + 
        (POW(a.latitude -'-2.97784', 2) * 53.1 * 53.1) 
     )) AS distance
 FROM adverts a
     WHERE a.type_id = 3
     HAVING distance < 25
     LIMIT 0 , 30

上記のクエリは、レコード0.0008 secを含むテーブル スキーマで実行されるため (同じテーブル スキーマでテストされたクエリは 20 分かかりました)、パフォーマンスが大幅に向上しました。10,0000.0129 sec

その他の最適化のヒント

  • の代わりに SELECT ステートメントで実際の列名を使用すると、SQL クエリが高速になります*
  • テーブル名を完全に参照しますmydatabase.mytable
  • ORDER BY使用する必要がある場合primary key(そのフィールドであるか、意図したフィールドで をindexed作成します)。indexORDERING
  • 数学計算に mysql フレームワーク関数を使用すると、プロセスが高速化されます。
  • そして最後に、これらの手順を使用してクエリをできるだけ単純にしてみてください (単純であるほど高速です)。

ソース

于 2012-09-19T12:55:44.423 に答える