2

クエリ結果を高速化するために、テーブルを最適化したいと思います。現在、InnoDBの代わりにMyISAMを使用しています。

問題は、最初の郵便番号は5文字の長さで、 A文字で始まらないということです。これらの郵便番号はすべて、A文字で始まります。長さは6文字または7文字で、テーブルレコードのほぼ中央にあります。私のテーブルのレコードはAZ順である必要がありますか、それとも郵便番号の長さ(テーブルの上部にリストされている文字が少ないレコード)の方が最適化に適していると思いますか?

または、他に何を提案しますか?

テーブル構造:

CREATE TABLE `postcodes` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `postcode` varchar(10) NOT NULL,
  `latitude` decimal(25,20) NOT NULL,
  `longitude` decimal(25,20) NOT NULL,
  `fk_areas_id` int(11) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `unique_postcodes_postcode` (`postcode`) USING HASH
) ENGINE=MyISAM AUTO_INCREMENT=1696089 DEFAULT CHARSET=utf8;

質問:

SELECT
                    (@rownum := @rownum + 1) AS No,
                    postcode AS Postcode,
                    latitude AS Latitude,
                    longitude AS Longitude,
                    (
                        (
                            ACOS(SIN((SELECT latitude FROM postcodes WHERE postcode = $postcode) * PI() / 180) * SIN(latitude * PI() / 180) +
                            COS((SELECT latitude FROM postcodes WHERE postcode = $postcode) * PI() / 180) * COS(latitude * PI() / 180) *
                            COS(((SELECT longitude FROM postcodes WHERE postcode = $postcode) - longitude) * PI() / 180)) * 180 / PI()
                        ) * 60 * 1.1515
                    ) AS Distance
                FROM postcodes, (SELECT @rownum := 0) AS No
                HAVING Distance <= 0.5 /*miles*/
                ORDER BY Distance ASC
4

2 に答える 2

1

インデックスが付いたマテリアライズドビューを使用することをお勧めします。COSまたはのような関数を実行するときはいつでもSIN、データベースはそれを新しく計算する必要があるため、インデックスは無視されます。ソリューションを事前に計算するマテリアライズドビュー(MySQLでは単なるビューだと思います)を作成する必要があります。そのソリューションが計算されたら、マテリアライズドビューにインデックスを付け、それに対してクエリを実行します。

どうやらMySQLでそれを行う方法は次のとおりです:

Create table computed_view  
  --Complex and lengthy sql here  
create index on foo
create index on bar
create index on baz

次に、次のようにします。

select * from computed_view where foo = ? and bar = ?

または、さらに簡単なソリューション。挿入する前に距離を計算して、データベースが単なるデータリポジトリ(処理方法)になるようにします。データベースで数学的な計算をしていることに気付いた場合は、間違った道を進んでいます。これをPHPまたは使用している言語にオフセットしてから、計算値を保持します。

于 2012-11-06T16:56:16.380 に答える
0

レコードの順序はおそらく影響力がありません。クエリを単純化することで何かを最適化したり、値を事前に計算することで何かを最適化したり、必要最小限の精度でデータを保存したりすることでさらに最適化できます。

ランダムに生成された約10,000の郵便番号を使用していくつかのテストを実行しましたが、上記のすべてとこのインデックスを使用すると、(約)25%のパフォーマンスの向上が見られます。

CREATE INDEX postcodes_ndx ON postcodes(postcode, latitude, longitude);

結果は、プラットフォームやその他のパラメーターだけでなく、各行に存在する他のデータによって異なります。

MySQLの空間拡張を活用する可能性も検討してください。それ以外の場合は、郵便番号ごとにオフザカフのUTM位置を保存し(ロシアほど広い領域をカバーしていない限り)、最初のテーブルを一辺3マイルの正方形内の値に制限してみてください。 、を中心に$postcode。これにより、取得された行がすぐに数桁減少し、それに比例してクエリ速度が向上します。

副選択の代わりにJOINから始めました:

SELECT
(@rownum := @rownum + 1) AS No,
A.postcode AS Postcode,
A.latitude AS Latitude,
A.longitude AS Longitude,
ACOS(
        SIN(B.latitude * PI() / 180) * SIN(A.latitude * PI() / 180) +
        COS(B.latitude * PI() / 180) * COS(A.latitude * PI() / 180) *
        COS((B.longitude - A.longitude) * PI() / 180)
) * 180 / PI() * 60 * 1.1515
AS Distance
FROM postcodes AS A,
(SELECT * FROM postcodes WHERE postcode = $postcode) AS B,
(SELECT @rownum := 0) AS No
HAVING Distance <= 0.5 /*miles*/
ORDER BY Distance ASC;

また、緯度と経度をあまりにも正確に保存していると思います。

3桁と小数点以下6桁の場所があると、それは約1インチの精度であり、使用している数式にはそれよりも優れた誤差があります。

また、緯度と経度を度ではなく放射で保存することで、パフォーマンスを低下させることができる場合があります。そうすれば、ほとんどのPI()/180計算を保存できます。これはトリガーを使用して行うことができ、andを使用して2つの追加の列を格納できます(それぞれ、latlat_radおよびlng_radlngよりも小数点以下3桁が必要です)。

3958.57一部の値を事前に計算することもできます。たとえば、の代わりに直接乗算できるarccosなどです180/PI()*60*1.1515

JOIN内でトリガー計算の一部を移動することもできます。

SELECT A.postcode AS Postcode,    
       A.latitude AS Latitude,
       A.longitude AS Longitude,
      ACOS(
        sinlat * SIN(A.latitude * PI() / 180)
      + coslat * COS(A.latitude * PI() / 180)
      * COS((B.longitude - A.longitude) * PI() / 180)
      ) * 3958.57 AS Distance
FROM postcodes AS A,
(SELECT latitude, longitude,
 COS(latitude*PI()/180) AS coslat,
 SIN(latitude*PI()/180) AS sinlat
 FROM postcodes WHERE postcode = $postcode
) AS B HAVING Distance <= 0.5
ORDER BY Distance ASC;

@rownum最後に、計算を削除してPHPに追加し直すことができます。

$rownum = 1;
while($tuple = SQLFetchTuple($exec))
{
    $tuple['No'] = $rownum++;
    ... same code as before...
}

最初のテーブルをトリミングします

これは実際には空間拡張の恩恵を受けますが、郵便番号の最初のグループを最初のグループから10分の1度以内に強制することができます。

もちろん、赤道にいない限り、2つの距離は同じではありません。安全なマージンを確保するために、約2マイルまたは3マイルに対応する緯度と経度のデルタを計算できます。

SELECT A.postcode AS Postcode,
   A.latitude AS Latitude,
   A.longitude AS Longitude,
   ACOS(
       sinlat * SIN(A.latitude * PI() / 180) +
       coslat * COS(A.latitude * PI() / 180) * COS((B.longitude - A.longitude) * PI() / 180)
   ) * 3958.57 AS Distance
FROM postcodes AS A,
(SELECT latitude, longitude,
  COS(latitude*PI()/180) AS coslat,
  SIN(latitude*PI()/180) AS sinlat
 FROM postcodes WHERE postcode = $postcode) AS B
WHERE
    ABS(A.latitude  - B.latitude ) < 0.1
AND ABS(A.longitude - B.longitude) < 0.1
HAVING Distance <= 0.5

距離ASCで注文;

于 2012-11-06T16:37:13.797 に答える