32

PostgreSQL 8.4.9 を使用して、クエリに対する PostgreSQL のパフォーマンスに奇妙な問題があります。LEFT OUTER JOINこのクエリは、関連 ID が存在する関連 ID 列を追加するためにを使用して、3D ボリューム内のポイントのセットを選択しています。範囲を少し変更すると、xPostgreSQL が別のクエリ プランを選択する可能性があり、実行時間が 0.01 秒から 50 秒になります。これが問題のクエリです。

SELECT treenode.id AS id,
       treenode.parent_id AS parentid,
       (treenode.location).x AS x,
       (treenode.location).y AS y,
       (treenode.location).z AS z,
       treenode.confidence AS confidence,
       treenode.user_id AS user_id,
       treenode.radius AS radius,
       ((treenode.location).z - 50) AS z_diff,
       treenode_class_instance.class_instance_id AS skeleton_id
  FROM treenode LEFT OUTER JOIN
         (treenode_class_instance INNER JOIN
          class_instance ON treenode_class_instance.class_instance_id
                                                  = class_instance.id
                            AND class_instance.class_id = 7828307)
       ON (treenode_class_instance.treenode_id = treenode.id
           AND treenode_class_instance.relation_id = 7828321)
  WHERE treenode.project_id = 4
    AND (treenode.location).x >= 8000
    AND (treenode.location).x <= (8000 + 4736)
    AND (treenode.location).y >= 22244
    AND (treenode.location).y <= (22244 + 3248)
    AND (treenode.location).z >= 0
    AND (treenode.location).z <= 100
  ORDER BY parentid DESC, id, z_diff
  LIMIT 400;

そのクエリには 1 分近くかかります。そのクエリEXPLAINの前に追加すると、次のクエリ プランが使用されているようです。

 Limit  (cost=56185.16..56185.17 rows=1 width=89)
   ->  Sort  (cost=56185.16..56185.17 rows=1 width=89)
         Sort Key: treenode.parent_id, treenode.id, (((treenode.location).z - 50::double precision))
         ->  Nested Loop Left Join  (cost=6715.16..56185.15 rows=1 width=89)
               Join Filter: (treenode_class_instance.treenode_id = treenode.id)
               ->  Bitmap Heap Scan on treenode  (cost=148.55..184.16 rows=1 width=81)
                     Recheck Cond: (((location).x >= 8000::double precision) AND ((location).x <= 12736::double precision) AND ((location).z >= 0::double precision) AND ((location).z <= 100::double precision))
                     Filter: (((location).y >= 22244::double precision) AND ((location).y <= 25492::double precision) AND (project_id = 4))
                     ->  BitmapAnd  (cost=148.55..148.55 rows=9 width=0)
                           ->  Bitmap Index Scan on location_x_index  (cost=0.00..67.38 rows=2700 width=0)
                                 Index Cond: (((location).x >= 8000::double precision) AND ((location).x <= 12736::double precision))
                           ->  Bitmap Index Scan on location_z_index  (cost=0.00..80.91 rows=3253 width=0)
                                 Index Cond: (((location).z >= 0::double precision) AND ((location).z <= 100::double precision))
               ->  Hash Join  (cost=6566.61..53361.69 rows=211144 width=16)
                     Hash Cond: (treenode_class_instance.class_instance_id = class_instance.id)
                     ->  Seq Scan on treenode_class_instance  (cost=0.00..25323.79 rows=969285 width=16)
                           Filter: (relation_id = 7828321)
                     ->  Hash  (cost=5723.54..5723.54 rows=51366 width=8)
                           ->  Seq Scan on class_instance  (cost=0.00..5723.54 rows=51366 width=8)
                                 Filter: (class_id = 7828307)
(20 rows)

ただし、範囲内条件を に置き換える8000x10644クエリは一瞬で実行され、次のクエリ プランが使用されます。

 Limit  (cost=58378.94..58378.95 rows=2 width=89)
   ->  Sort  (cost=58378.94..58378.95 rows=2 width=89)
         Sort Key: treenode.parent_id, treenode.id, (((treenode.location).z - 50::double precision))
         ->  Hash Left Join  (cost=57263.11..58378.93 rows=2 width=89)
               Hash Cond: (treenode.id = treenode_class_instance.treenode_id)
               ->  Bitmap Heap Scan on treenode  (cost=231.12..313.44 rows=2 width=81)
                     Recheck Cond: (((location).z >= 0::double precision) AND ((location).z <= 100::double precision) AND ((location).x >= 10644::double precision) AND ((location).x <= 15380::double precision))
                     Filter: (((location).y >= 22244::double precision) AND ((location).y <= 25492::double precision) AND (project_id = 4))
                     ->  BitmapAnd  (cost=231.12..231.12 rows=21 width=0)
                           ->  Bitmap Index Scan on location_z_index  (cost=0.00..80.91 rows=3253 width=0)
                                 Index Cond: (((location).z >= 0::double precision) AND ((location).z <= 100::double precision))
                           ->  Bitmap Index Scan on location_x_index  (cost=0.00..149.95 rows=6157 width=0)
                                 Index Cond: (((location).x >= 10644::double precision) AND ((location).x <= 15380::double precision))
               ->  Hash  (cost=53361.69..53361.69 rows=211144 width=16)
                     ->  Hash Join  (cost=6566.61..53361.69 rows=211144 width=16)
                           Hash Cond: (treenode_class_instance.class_instance_id = class_instance.id)
                           ->  Seq Scan on treenode_class_instance  (cost=0.00..25323.79 rows=969285 width=16)
                                 Filter: (relation_id = 7828321)
                           ->  Hash  (cost=5723.54..5723.54 rows=51366 width=8)
                                 ->  Seq Scan on class_instance  (cost=0.00..5723.54 rows=51366 width=8)
                                       Filter: (class_id = 7828307)
(21 rows)

私はこれらのクエリ プランの解析の専門家にはほど遠いですが、明確な違いは、あるx範囲ではHash Left Join(LEFT OUTER JOIN非常に高速な) を使用し、他の範囲では (非常に高速なNested Loop Left Joinようです) を使用することです。スロー)。どちらの場合も、クエリは約 90 行を返します。SET ENABLE_NESTLOOP TO FALSE遅いバージョンのクエリの前に行うと、非常に高速になりますが、一般的にその設定を使用することは悪い考えであることを理解しています.

たとえば、クエリ プランナーが明らかにより効率的な戦略を選択する可能性を高めるために、特定のインデックスを作成できますか? なぜ PostgreSQL のクエリ プランナーがこれらのクエリの 1 つに対してこのような貧弱な戦略を選択しなければならないのか、誰かが提案できますか? 以下に、役立つと思われるスキーマの詳細を示します。


treenode テーブルには 900,000 行あり、次のように定義されています。

                                     Table "public.treenode"
    Column     |           Type           |                      Modifiers                       
---------------+--------------------------+------------------------------------------------------
 id            | bigint                   | not null default nextval('concept_id_seq'::regclass)
 user_id       | bigint                   | not null
 creation_time | timestamp with time zone | not null default now()
 edition_time  | timestamp with time zone | not null default now()
 project_id    | bigint                   | not null
 location      | double3d                 | not null
 parent_id     | bigint                   | 
 radius        | double precision         | not null default 0
 confidence    | integer                  | not null default 5
Indexes:
    "treenode_pkey" PRIMARY KEY, btree (id)
    "treenode_id_key" UNIQUE, btree (id)
    "location_x_index" btree (((location).x))
    "location_y_index" btree (((location).y))
    "location_z_index" btree (((location).z))
Foreign-key constraints:
    "treenode_parent_id_fkey" FOREIGN KEY (parent_id) REFERENCES treenode(id)
Referenced by:
    TABLE "treenode_class_instance" CONSTRAINT "treenode_class_instance_treenode_id_fkey" FOREIGN KEY (treenode_id) REFERENCES treenode(id) ON DELETE CASCADE
    TABLE "treenode" CONSTRAINT "treenode_parent_id_fkey" FOREIGN KEY (parent_id) REFERENCES treenode(id)
Triggers:
    on_edit_treenode BEFORE UPDATE ON treenode FOR EACH ROW EXECUTE PROCEDURE on_edit()
Inherits: location

複合型は次のdouble3dように定義されます。

Composite type "public.double3d"
 Column |       Type       
--------+------------------
 x      | double precision
 y      | double precision
 z      | double precision

結合に関係する他の 2 つのテーブルは次のtreenode_class_instanceとおりです。

                               Table "public.treenode_class_instance"
      Column       |           Type           |                      Modifiers                       
-------------------+--------------------------+------------------------------------------------------
 id                | bigint                   | not null default nextval('concept_id_seq'::regclass)
 user_id           | bigint                   | not null
 creation_time     | timestamp with time zone | not null default now()
 edition_time      | timestamp with time zone | not null default now()
 project_id        | bigint                   | not null
 relation_id       | bigint                   | not null
 treenode_id       | bigint                   | not null
 class_instance_id | bigint                   | not null
Indexes:
    "treenode_class_instance_pkey" PRIMARY KEY, btree (id)
    "treenode_class_instance_id_key" UNIQUE, btree (id)
    "idx_class_instance_id" btree (class_instance_id)
Foreign-key constraints:
    "treenode_class_instance_class_instance_id_fkey" FOREIGN KEY (class_instance_id) REFERENCES class_instance(id) ON DELETE CASCADE
    "treenode_class_instance_relation_id_fkey" FOREIGN KEY (relation_id) REFERENCES relation(id)
    "treenode_class_instance_treenode_id_fkey" FOREIGN KEY (treenode_id) REFERENCES treenode(id) ON DELETE CASCADE
    "treenode_class_instance_user_id_fkey" FOREIGN KEY (user_id) REFERENCES "user"(id)
Triggers:
    on_edit_treenode_class_instance BEFORE UPDATE ON treenode_class_instance FOR EACH ROW EXECUTE PROCEDURE on_edit()
Inherits: relation_instance

...そしてclass_instance

                                  Table "public.class_instance"
    Column     |           Type           |                      Modifiers                       
---------------+--------------------------+------------------------------------------------------
 id            | bigint                   | not null default nextval('concept_id_seq'::regclass)
 user_id       | bigint                   | not null
 creation_time | timestamp with time zone | not null default now()
 edition_time  | timestamp with time zone | not null default now()
 project_id    | bigint                   | not null
 class_id      | bigint                   | not null
 name          | character varying(255)   | not null
Indexes:
    "class_instance_pkey" PRIMARY KEY, btree (id)
    "class_instance_id_key" UNIQUE, btree (id)
Foreign-key constraints:
    "class_instance_class_id_fkey" FOREIGN KEY (class_id) REFERENCES class(id)
    "class_instance_user_id_fkey" FOREIGN KEY (user_id) REFERENCES "user"(id)
Referenced by:
    TABLE "class_instance_class_instance" CONSTRAINT "class_instance_class_instance_class_instance_a_fkey" FOREIGN KEY (class_instance_a) REFERENCES class_instance(id) ON DELETE CASCADE
    TABLE "class_instance_class_instance" CONSTRAINT "class_instance_class_instance_class_instance_b_fkey" FOREIGN KEY (class_instance_b) REFERENCES class_instance(id) ON DELETE CASCADE
    TABLE "connector_class_instance" CONSTRAINT "connector_class_instance_class_instance_id_fkey" FOREIGN KEY (class_instance_id) REFERENCES class_instance(id)
    TABLE "treenode_class_instance" CONSTRAINT "treenode_class_instance_class_instance_id_fkey" FOREIGN KEY (class_instance_id) REFERENCES class_instance(id) ON DELETE CASCADE
Triggers:
    on_edit_class_instance BEFORE UPDATE ON class_instance FOR EACH ROW EXECUTE PROCEDURE on_edit()
Inherits: concept
4

6 に答える 6

60

クエリ プランナーが誤った決定を下した場合、それは主に次の 2 つのいずれかです。

1.統計が不正確です。

あなたはANALYZE十分に走りますか?合体 形 も 人気VACUUM ANALYZEautovacuumがオンの場合(これは現代の Postgres のデフォルトです)、ANALYZE自動的に実行されます。ただし、次のことを考慮してください。

(上位 2 つの回答は Postgres 12 にも適用されます。)

テーブルが大きく、データの分散が不規則な場合は、値を上げるとdefault_statistics_target役立つ場合があります。むしろ、関連する列の統計ターゲットを設定するだけです(基本的に、クエリの WHEREまたは句内のもの):JOIN

ALTER TABLE ... ALTER COLUMN ... SET STATISTICS 400;  -- calibrate number

ターゲットは 0 ~ 10000 の範囲で設定できます。

ANALYZEその後、(関連するテーブルで)再度 実行します。

2. プランナー見積もりの​​コスト設定がオフになっている。

マニュアルの Planner Cost Constantsの章を読んでください。

この一般的に役立つ PostgreSQL Wiki ページのdefault_statistics_targetrandom_page_costの章を見てください。

他にも考えられる理由はたくさんありますが、これらは最も一般的なものです。

于 2011-11-22T15:07:25.953 に答える
8

データベース統計とカスタム データ型の組み合わせを考慮しない限り、これが悪い統計と関係があるとは思えません。

私の推測では、PostgreSQL はネストされたループ結合を選択しています。これは、述語(treenode.location).x >= 8000 AND (treenode.location).x <= (8000 + 4736)を見て、比較の算術演算で奇妙なことを行うためです。ネストされたループは通常、結合の内側に少量のデータがある場合に使用されます。

しかし、定数を 10736 に切り替えると、別の計画が得られます。計画が非常に複雑で、Genetic Query Optimization (GEQO)が開始され、非決定論的な計画構築の副作用が発生する可能性は常にあります。クエリの評価の順序に十分な矛盾があり、それが起こっていると思います。

1 つのオプションは、アドホック コードを使用する代わりに、パラメーター化された/準備されたステートメントを使用してこれを調べることです。3 次元空間で作業しているため、 PostGISの使用も検討したい場合があります。やり過ぎかもしれませんが、これらのクエリを適切に実行するために必要なパフォーマンスを提供できる場合もあります。

プランナーの行動を強制することは最良の選択ではありませんが、ソフトウェアよりも優れた決定を下すことがあります。

于 2011-11-23T02:42:13.783 に答える
2

アーウィンが統計について言ったこと。また:

ORDER BY parentid DESC, id, z_diff

並べ替え中

parentid DESC, id, z

オプティマイザーがシャッフルする余地をもう少し与えるかもしれません。(最終学期なのであまり関係ないと思いますし、そんなに高くないですが、やってみてはいかがでしょうか)

于 2011-11-22T15:29:36.457 に答える
2

それがあなたの問題の原因であるとは断言できませんが、バージョン 8.4.8 と 8.4.9 の間で postgres クエリ プランナーに変更が加えられたようです。古いバージョンを使用してみて、違いがあるかどうかを確認してください。

http://postgresql.1045698.n5.nabble.com/BUG-6275-Horrible-performance-regression-td4944891.html

バージョンを変更した場合は、テーブルを再分析することを忘れないでください。

于 2011-11-28T19:01:43.933 に答える