2

私は2つのテーブルを持っています。それらの構造はおおまかに次のとおりです。でも、名前を変えました。

CREATE TABLE overlay_polygon
(
  overlay_polygon_id SERIAL PRIMARY KEY,
  some_other_polygon_id INTEGER REFERENCES some_other_polygon (some_other_polygon_id)
  dollar_value NUMERIC,
  geom GEOMETRY(Polygon,26915)
)

CREATE TABLE point
(
  point_id SERIAL PRIMARY KEY,
  some_other_polygon_id INTEGER REFERENCES some_other_polygon (some_other_polygon_id)
  -- A bunch of other fields that this query won't touch
  geom GEOMETRY(Point,26915)
)

pointgeomその列には、という名前の空間インデックスがあり、その列にもspix_pointインデックスがありsome_other_polygon_idます。

には約500,000行ありpoint、のほとんどすべての行がのpoint一部の行と交差していoverlay_polygonます。元々、私のoverlay_polygonテーブルには、非常に小さな領域(ほとんどの場合、1平方メートル未満)があり、からの行と空間的に交差しないいくつかの行が含まれていましたpoint。のどの行とも交差しない小さな行を削除すると、point38行になります。

名前が示すように、overlay_polygonは他の3つのテーブル(を含むsome_other_polygon)からのポリゴンのオーバーレイの結果として生成されたポリゴンのテーブルです。特に、を使用していくつかの計算を行う必要がありdollar_valueますpoint。将来、処理を高速化するために、ポイントと交差しない行を削除しようとすると、行の数を照会することになりました。最も明白なクエリは次のように思われました。

SELECT op.*, COUNT(point_id) AS num_points
FROM overlay_polygon op
LEFT JOIN point ON op.some_other_polygon_id = point.some_other_polygon_id AND ST_Intersects(op.geom, point.geom)
GROUP BY op.overlay_polygon_id
ORDER BY op.overlay_polygon_id
;

これがそのEXPLAIN (ANALYZE, BUFFERS)です。

GroupAggregate  (cost=544.45..545.12 rows=38 width=8049) (actual time=284962.944..540959.914 rows=38 loops=1)
  Buffers: shared hit=58694 read=17119, temp read=189483 written=189483
  I/O Timings: read=39171.525
  ->  Sort  (cost=544.45..544.55 rows=38 width=8049) (actual time=271754.952..534154.573 rows=415224 loops=1)
        Sort Key: op.overlay_polygon_id
        Sort Method: external merge  Disk: 897016kB
        Buffers: shared hit=58694 read=17119, temp read=189483 written=189483
        I/O Timings: read=39171.525
        ->  Nested Loop Left Join  (cost=0.00..543.46 rows=38 width=8049) (actual time=0.110..46755.284 rows=415224 loops=1)
              Buffers: shared hit=58694 read=17119
              I/O Timings: read=39171.525
              ->  Seq Scan on overlay_polygon op  (cost=0.00..11.38 rows=38 width=8045) (actual time=0.043..153.255 rows=38 loops=1)
                    Buffers: shared hit=1 read=10
                    I/O Timings: read=152.866
              ->  Index Scan using spix_point on point  (cost=0.00..13.99 rows=1 width=200) (actual time=50.229..1139.868 rows=10927 loops=38)
                    Index Cond: (op.geom && geom)
                    Filter: ((op.some_other_polygon_id = some_other_polygon_id) AND _st_intersects(op.geom, geom))
                    Rows Removed by Filter: 13353
                    Buffers: shared hit=58693 read=17109
                    I/O Timings: read=39018.660
Total runtime: 542172.156 ms

ただし、このクエリははるかに高速に実行されることがわかりました。

SELECT *
FROM overlay_polygon
JOIN (SELECT op.overlay_polygon_id, COUNT(point_id) AS num_points
      FROM overlay_polygon op
      LEFT JOIN point ON op.some_other_polygon_id = point.some_other_polygon_id AND ST_Intersects(op.geom, point.geom)
      GROUP BY op.overlay_polygon_id
     ) x ON x.overlay_polygon_id = overlay_polygon.overlay_polygon_id
ORDER BY overlay_polygon.overlay_polygon_id
;

そのEXPLAIN (ANALYZE, BUFFERS)下にあります。

Sort  (cost=557.78..557.88 rows=38 width=8057) (actual time=18904.661..18904.748 rows=38 loops=1)
  Sort Key: overlay_polygon.overlay_polygon_id
  Sort Method: quicksort  Memory: 126kB
  Buffers: shared hit=58690 read=17134
  I/O Timings: read=9924.328
  ->  Hash Join  (cost=544.88..556.78 rows=38 width=8057) (actual time=18903.697..18904.210 rows=38 loops=1)
        Hash Cond: (overlay_polygon.overlay_polygon_id = op.overlay_polygon_id)
        Buffers: shared hit=58690 read=17134
        I/O Timings: read=9924.328
        ->  Seq Scan on overlay_polygon  (cost=0.00..11.38 rows=38 width=8045) (actual time=0.127..0.411 rows=38 loops=1)
              Buffers: shared hit=2 read=9
              I/O Timings: read=0.173
        ->  Hash  (cost=544.41..544.41 rows=38 width=12) (actual time=18903.500..18903.500 rows=38 loops=1)
              Buckets: 1024  Batches: 1  Memory Usage: 2kB
              Buffers: shared hit=58688 read=17125
              I/O Timings: read=9924.154
              ->  HashAggregate  (cost=543.65..544.03 rows=38 width=8) (actual time=18903.276..18903.379 rows=38 loops=1)
                    Buffers: shared hit=58688 read=17125
                    I/O Timings: read=9924.154
                    ->  Nested Loop Left Join  (cost=0.00..543.46 rows=38 width=8) (actual time=0.052..17169.606 rows=415224 loops=1)
                          Buffers: shared hit=58688 read=17125
                          I/O Timings: read=9924.154
                          ->  Seq Scan on overlay_polygon op  (cost=0.00..11.38 rows=38 width=8038) (actual time=0.004..0.537 rows=38 loops=1)
                                Buffers: shared hit=1 read=10
                                I/O Timings: read=0.279
                          ->  Index Scan using spix_point on point  (cost=0.00..13.99 rows=1 width=200) (actual time=4.422..381.991 rows=10927 loops=38)
                                Index Cond: (op.gopm && gopm)
                                Filter: ((op.some_other_polygon_id = some_other_polygon_id) AND _st_intersects(op.geom, geom))
                                Rows Removed by Filter: 13353
                                Buffers: shared hit=58687 read=17115
                                I/O Timings: read=9923.875
Total runtime: 18905.293 ms

ご覧のとおり、これらのコスト見積もりがどれほど正確かはわかりませんが、同等のコスト見積もりがあります。PostGIS関数を含むコスト見積もりについては疑わしいです。両方のテーブルはVACUUM ANALYZE FULL、最後に変更されてからクエリを実行する前に実行されています。

おそらく私は自分EXPLAIN ANALYZEのを読むことができないのかもしれませんが、なぜこれらのクエリの実行時間が大幅に異なるのかわかりません。誰かが何かを識別できますか?私が考えることができる唯一の可能性は、に含まれる列の数に関連していますLEFT JOIN

編集1

@ChrisTraversの提案work_memに従って、最初のクエリを増やして再実行しました。これが大きな改善を表すとは思わない。

実行

SET work_mem='4MB';

(1MBでした。)

次に、最初のクエリを実行すると、これらの結果が得られました。

GroupAggregate  (cost=544.45..545.12 rows=38 width=8049) (actual time=339910.046..495775.478 rows=38 loops=1)
  Buffers: shared hit=58552 read=17261, temp read=112133 written=112133
  ->  Sort  (cost=544.45..544.55 rows=38 width=8049) (actual time=325391.923..491329.208 rows=415224 loops=1)
        Sort Key: op.overlay_polygon_id
        Sort Method: external merge  Disk: 896904kB
        Buffers: shared hit=58552 read=17261, temp read=112133 written=112133
        ->  Nested Loop Left Join  (cost=0.00..543.46 rows=38 width=8049) (actual time=14.698..234266.573 rows=415224 loops=1)
              Buffers: shared hit=58552 read=17261
              ->  Seq Scan on overlay_polygon op  (cost=0.00..11.38 rows=38 width=8045) (actual time=14.612..15.384 rows=38 loops=1)
                    Buffers: shared read=11
              ->  Index Scan using spix_point on point  (cost=0.00..13.99 rows=1 width=200) (actual time=95.262..5451.636 rows=10927 loops=38)
                    Index Cond: (op.geom && geom)
                    Filter: ((op.some_other_polygon_id = some_other_polygon_id) AND _st_intersects(op.geom, geom))
                    Rows Removed by Filter: 13353
                    Buffers: shared hit=58552 read=17250
Total runtime: 496936.775 ms

編集2

さて、これは私が以前は気づかなかった素敵な大きな匂いです(主にANALYZE出力を読むのに苦労しているためです)。申し訳ありませんが、すぐには気づきませんでした。

Sort  (cost=544.45..544.55 rows=38 width=8049) (actual time=271754.952..534154.573 rows=415224 loops=1)

推定行数:38。実際の行数:400K以上。アイデア、誰か?

4

2 に答える 2

2

私の当面の考えは、これはwork_memの制限に関係している可能性があるということです。プランの違いは、最初に結合してから集約し、2番目に集約して結合することです。これは、集約セットが狭くなることを意味します。つまり、その操作で使用されるメモリが少なくなります。

work_memを2倍にして再試行すると、何が変わるかを確認するのは興味深いことです。

編集: work_memを増やすとわずかな改善しか得られないことがわかったので、次の問題はソート行の見積もりです。実際、ここではwork_memを超えていると思われます。これは、38行しか期待できないため簡単であると期待していますが、代わりに多くの行を取得します。プランナーがこの情報をどこで取得しているのかはわかりません。プランナーが(正しく)38行が集計から予想される行数であると推定しているためです。この部分は私にはプランナーのバグのように見え始めていますが、私はそれに指を置くのに苦労しています。pgsql-generalのメーリングリストに書き込んで掲載する価値があるかもしれません。プランナーがソートに必要なメモリとアグリゲートに必要なメモリの間で混乱しているように私にはほとんど見えます。

于 2013-02-28T03:56:41.557 に答える
1

で概説したようEDIT 2に、実際、返される行の推定数と実際の数の間には大きな不一致があります。しかし、問題の原因はツリーの少し下にあります。

Index Scan using spix_point on point  (cost=0.00..13.99 rows=1 width=200) 
   (actual time=95.262..5451.636 rows=10927 loops=38)

これは、ツリーのすべてのノードに影響しNested LoopますSort

私は次のことをしようとします:

  1. まず、統計が最新であることを確認します。

    VACUUM ANALYZE point;
    VACUUM ANALYZE overlay_polygon;
    
  2. 運が悪い場合は、列の統計ターゲットを増やします。geometry

    ALTER TABLE point ALTER geom SET STATISTICS 500;
    ALTER TABLE overlay_polygon ALTER geom SET STATISTICS 1500;
    

    そして、テーブルをもう一度分析します。

  3. IMHOは、Nested Loopsここでは良くありませんが、より適切Hashでしょう。発行してみてください:

    SET enable_nestloop TO off;
    

    セッションレベルで、それが役立つかどうかを確認します。


クエリをもう少し調べた後、some_other_polygon_id列の統計ターゲットを増やす価値があると思います。

ALTER TABLE point ALTER some_other_polygon_id SET STATISTICS 5000;

また、2番目のクエリが最初のクエリよりもはるかに高速である理由はわかりません。両方のクエリが1回だけ実行され、「コールド」データベースで実行されたと言っているのは正しいですか?2番目のクエリはOSファイルシステムキャッシュを利用したため、はるかに高速に実行されたと実感しています。

完全にスキャンされて完全に実行されるためspix_point、ここでの計画担当者による使用は不適切な決定です。したがって、クエリを改善する1つの方法は、このテーブルを強制することです。これは:の助けを借りて行うことができます:point LEFT JOINSeq ScanCTE

WITH p AS (SELECT point_id, some_other_polygon_id, geom FROM point)
SELECT op.*, COUNT(p.point_id) AS num_points
  FROM overlay_polygon op
  LEFT JOIN p ON op.some_other_polygon_id = p.some_other_polygon_id
       AND ST_Intersects(op.geom, p.geom)
 GROUP BY op.overlay_polygon_id
 ORDER BY op.overlay_polygon_id;

しかし、これは具体化の分野で減速するでしょう。それでも、試してみてください。

于 2013-02-28T07:43:49.137 に答える