MySQLをあきらめた後、Elasticsearchを試してみましたが、PostgreSQL/PostGISを使用して、PostgreSQLのみを使用できるかどうかを確認したくありません。
テーブルからレコードを距離でフェッチし(正確である必要はありません)、距離でソートする必要があります。テーブルには1000万のレコードがあります。
PostgreSQLでのクエリの速度が遅いので、MySQLでの速度が遅いので、何か間違っている必要があると思います。
私は何ができるのですか?
テーブル:
id | hash_id | town | geo_pt2
geo_pt2 is geography
インデックス:
CREATE INDEX geo_pt2_gix ON public.member_profile USING Gist (geo_pt2)
クエリ:
SELECT hash_id, town
, ST_Distance(t.x, geo_pt2) AS dist
FROM member_profile, (SELECT ST_GeographyFromText('POINT(47.4667 8.3167)')) AS t(x)
WHERE ST_DWithin(t.x, geo_pt2, 250000)
ORDER BY dist
limit 100 offset 1000;
説明:
Limit (cost=9.08..9.08 rows=1 width=53)
-> Sort (cost=9.07..9.08 rows=1 width=53)
Sort Key: (_st_distance('0101000020E610000088855AD3BCBB474052499D8026A22040'::geography, member_profile.geo_pt2, '0'::double precision, true))
-> Index Scan using geo_pt2_gix on member_profile (cost=0.42..9.06 rows=1 width=53)
Index Cond: (geo_pt2 && '0101000020E610000088855AD3BCBB474052499D8026A22040'::geography)
Filter: (('0101000020E610000088855AD3BCBB474052499D8026A22040'::geography && _st_expand(geo_pt2, '250000'::double precision)) AND _st_dwithin('0101000020E610000088855AD3BCBB474052499D8026A22040'::geography, geo_pt2, '250000'::double precision, true))
IOPS(NVMe)が高い最新のサーバーでPostgreSQL 10を使用していますが、クエリには35秒必要です。
@Evan Carrollの提案の後、はるかに優れたパフォーマンス:
EXPLAIN ANALYZE SELECT hash_id, town
, ST_Distance(ST_MakePoint(47.4667, 8.3167)::geography, geo_pt2) AS dist
FROM member_profile
WHERE ST_DWithin(ST_MakePoint(47.4667, 8.3167)::geography, geo_pt2, 250000)
ORDER BY ST_MakePoint(47.4667, 8.3167)::geography <-> geo_pt2
OFFSET 10000
FETCH NEXT 100 ROWS ONLY;
Limit (cost=9.31..18.21 rows=1 width=61) (actual time=392.608..394.138 rows=100 loops=1)
-> Index Scan using geo_pt2_gix on member_profile (cost=0.42..9.31 rows=1 width=61) (actual time=26.624..392.776 rows=10100 loops=1)
Index Cond: (geo_pt2 && '0101000020E610000088855AD3BCBB474052499D8026A22040'::geography)
Order By: (geo_pt2 <-> '0101000020E610000088855AD3BCBB474052499D8026A22040'::geography)
Filter: (('0101000020E610000088855AD3BCBB474052499D8026A22040'::geography && _st_expand(geo_pt2, '250000'::double precision)) AND _st_dwithin('0101000020E610000088855AD3BCBB474052499D8026A22040'::geography, geo_pt2, '250000'::double precision, true))
Planning time: 89.020 ms
Execution time: 395.039 ms
ユーザーがページネーションを終了して遅くなった場合:
EXPLAIN ANALYZE SELECT hash_id, town
, ST_Distance(ST_MakePoint(47.4667, 8.3167)::geography, geo_pt2) AS dist
FROM member_profile
WHERE ST_DWithin(ST_MakePoint(47.4667, 8.3167)::geography, geo_pt2, 250000)
ORDER BY ST_MakePoint(47.4667, 8.3167)::geography <-> geo_pt2
OFFSET 1000000
FETCH NEXT 100 ROWS ONLY;
Limit (cost=9.31..18.21 rows=1 width=61) (actual time=28872.156..28873.239 rows=100 loops=1)
-> Index Scan using geo_pt2_gix on member_profile (cost=0.42..9.31 rows=1 width=61) (actual time=32.441..28764.569 rows=1000100 loops=1)
Index Cond: (geo_pt2 && '0101000020E610000088855AD3BCBB474052499D8026A22040'::geography)
Order By: (geo_pt2 <-> '0101000020E610000088855AD3BCBB474052499D8026A22040'::geography)
Filter: (('0101000020E610000088855AD3BCBB474052499D8026A22040'::geography && _st_expand(geo_pt2, '250000'::double precision)) AND _st_dwithin('0101000020E610000088855AD3BCBB474052499D8026A22040'::geography, geo_pt2, '250000'::double precision, true))
Planning time: 50.979 ms
Execution time: 28875.403 ms
まず、EXPLAIN ANALYZE
(単なるEXPLAIN
ではなく)を使用して、\d
の結果をテーブルに表示します。 (psql)。最初のポイントとして、これは、
ST_GeographyFromText('POINT(47.4667 8.3167)')
ST_MakePoint(47.4667, 8.3167)::geography
と書く必要があります
ここでの問題はこのパターンです。
SELECT ST_Distance( ST_MakePoint(47.4667, 8.3167)::geography, geo_pt2) AS dist
...
ORDER BY dist
LIMIT 100 OFFSET 1000;
これを行うたびに、少なくとも1100行までの距離を計算する必要があります。とはいえ、遅くなることはありません。そのためには、すべての行でST_Distance
を計算する必要があるため、遅いです。 <->
演算子を使用してKNNを停止することができます。 MySQLはKNNをサポートしていません 。
SELECT hash_id, town
, ST_Distance(ST_MakePoint(47.4667, 8.3167)::geography, geo_pt2) AS dist
FROM member_profile
WHERE ST_DWithin(ST_MakePoint(47.4667, 8.3167)::geography, geo_pt2, 250000)
ORDER BY ST_MakePoint(47.4667, 8.3167)::geography <-> geo_pt2
OFFSET 1000
FETCH NEXT 100 ROWS ONLY;
スタイルの批評として、私は個人的に OFFSET/FETCH (標準化されたメソッド制限/オフセット)を好みます。
これが機能するかどうかはわかりません。しかし、試してみる価値があるかもしれません(更新してください)。
SELECT hash_id, town
, ST_Distance(ST_MakePoint(47.4667, 8.3167)::geography, geo_pt2) AS dist
, ST_MakePoint(47.4667, 8.3167)::geography <-> geo_pt2 AS myknn
FROM member_profile
WHERE ST_DWithin(ST_MakePoint(47.4667, 8.3167)::geography, geo_pt2, 250000)
AND ST_MakePoint(47.4667, 8.3167)::geography <-> geo_pt2 > OLD_VALUE
ORDER BY ST_MakePoint(47.4667, 8.3167)::geography <-> geo_pt2
FETCH NEXT 100 ROWS ONLY;
したがって、最初にこれを実行すると、myknn
の最後の値の値が保存され、次に2回目に実行すると、その値をOLD_VALUE
としてこの句で再生できます。
AND ST_MakePoint(47.4667, 8.3167)::geography <-> geo_pt2 > OLD_VALUE
したがって、実行するたびに、続行する新しいポイントを保存し、FETCH NEXT x ROWS ONLY
を使用します。
myknn
とdist
は同じであってもかまいませんが、どちらかを削除するだけで済みます。