PostgreSQL 8.4.9を使用して、クエリのPostgreSQLパフォーマンスに奇妙な問題があります。このクエリは、3Dボリューム内のポイントのセットを選択し、LEFT OUTER JOIN
を使用して、関連IDが存在する場所に関連ID列を追加します。 x
範囲の小さな変更により、PostgreSQLは別のクエリプランを選択する可能性があり、実行時間は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)
ただし、x
範囲条件の8000
を10644
に置き換えると、クエリは1秒未満で実行され、次のクエリプランを使用します。
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)
私はこれらのクエリプランの解析の専門家とはほど遠いですが、明確な違いは、1つのx
範囲ではHash Left Join
をLEFT OUTER JOIN
に使用していることです(これは非常に高速です) )、他の範囲ではNested Loop Left Join
を使用します(非常に遅いようです)。どちらの場合も、クエリは約90行を返します。遅いバージョンのクエリの前にSET ENABLE_NESTLOOP TO FALSE
を実行すると、非常に高速になりますが、 一般的にこの設定を使用することは悪い考えです と理解しています。
たとえば、クエリプランナーが明らかにより効率的な戦略を選択する可能性を高めるために、特定のインデックスを作成できますか?なぜPostgreSQLのクエリプランナーがこれらのクエリの1つに対してこのような貧弱な戦略を選択する必要があるのか、誰かが提案できますか?以下に、役立つ可能性のあるスキーマの詳細を示します。
ツリーノードテーブルには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
クエリプランナーが誤った判断を下した場合、それは主に次の2つのうちの1つです。
ANALYZE
は十分に実行できますか?合わせた形でも人気_VACUUM ANALYZE
。 autovacuum がオンの場合(これは現代のPostgresのデフォルトです)、ANALYZE
が自動的に実行されます。しかし考慮してください:
(上位2つの回答はPostgres 9.6にも適用されます。)
テーブルがbigでデータ分布がirregularの場合、 default_statistics_target
が役立つ場合があります。または、単に 統計ターゲットを設定する 関連する列(基本的には、クエリのWHERE
またはJOIN
句にあるもの):
ALTER TABLE ... ALTER COLUMN ... SET STATISTICS 400; -- calibrate number
ターゲットは0〜10000の範囲で設定できます。
その後、関連するテーブルでANALYZE
を再度実行します。
マニュアルの章 Planner Cost Constants を読んでください。
この章のdefault_statistics_targetおよびrandom_page_costの章を見てください 一般的に有用なPostgreSQL Wikiページ 。
考えられる理由は他にもたくさんありますが、これらは最も一般的なものです。
データベース統計とカスタムデータタイプの組み合わせを考慮しない限り、これは悪い統計と関係があるのではないかと疑っています。
私の推測では、PostgreSQLは述語_(treenode.location).x >= 8000 AND (treenode.location).x <= (8000 + 4736)
_を見て、比較の算術でファンキーなことをするので、ネストループ結合を選択しています。 ネストループは通常、結合の内側に少量のデータがある場合に使用されます。
ただし、定数を10736に切り替えると、別の計画になります。 Genetic Query Optimization(GEQO)が効果を発揮し、非決定的な計画の構築。クエリでの評価の順序には十分な差異があり、私はそれが起こっていると考えさせます。
1つのオプションは、アドホックコードを使用する代わりに、パラメーター化/準備されたステートメントを使用してこれを調べることです。 3次元空間で作業しているので、 PostGIS の使用を検討することもできます。やり過ぎかもしれませんが、これらのクエリを適切に実行するために必要なパフォーマンスを提供できる場合もあります。
プランナーの動作を強制することは最良の選択ではありませんが、ソフトウェアよりも優れた意思決定をすることもあります。
私はそれがあなたの問題の原因であるとは思いませんが、バージョン8.4.8と8.4.9の間でpostgresクエリプランナーにいくつかの変更が加えられたようです。古いバージョンを使用してみて、違いがあるかどうかを確認してください。
http://postgresql.1045698.n5.nabble.com/BUG-6275-Horrible-performance-regression-td4944891.html
バージョンを変更した場合は、テーブルを再分析することを忘れないでください。
アーウィンが統計について言ったこと。また:
ORDER BY parentid DESC, id, z_diff
並べ替え
parentid DESC, id, z
オプティマイザにシャッフルする余地をもう少し与えるかもしれません。 (最後の用語なのでそれほど問題にならないと思いますが、種類はそれほど高価ではありませんが、試してみることができます)