Note that there are some explanatory texts on larger screens.

plurals
  1. POKeep PostgreSQL from sometimes choosing a bad query plan
    primarykey
    data
    text
    <p>I have a strange problem with PostgreSQL performance for a query, using PostgreSQL 8.4.9. This query is selecting a set of points within a 3D volume, using a <code>LEFT OUTER JOIN</code> to add a related ID column where that related ID exists. Small changes in the <code>x</code> range can cause PostgreSQL to choose a different query plan, which takes the execution time from 0.01 seconds to 50 seconds. This is the query in question:</p> <pre><code>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 &gt;= 8000 AND (treenode.location).x &lt;= (8000 + 4736) AND (treenode.location).y &gt;= 22244 AND (treenode.location).y &lt;= (22244 + 3248) AND (treenode.location).z &gt;= 0 AND (treenode.location).z &lt;= 100 ORDER BY parentid DESC, id, z_diff LIMIT 400; </code></pre> <p>That query takes nearly a minute, and, if I add <code>EXPLAIN</code> to the front of that query, seems to be using the following query plan:</p> <pre><code> Limit (cost=56185.16..56185.17 rows=1 width=89) -&gt; Sort (cost=56185.16..56185.17 rows=1 width=89) Sort Key: treenode.parent_id, treenode.id, (((treenode.location).z - 50::double precision)) -&gt; Nested Loop Left Join (cost=6715.16..56185.15 rows=1 width=89) Join Filter: (treenode_class_instance.treenode_id = treenode.id) -&gt; Bitmap Heap Scan on treenode (cost=148.55..184.16 rows=1 width=81) Recheck Cond: (((location).x &gt;= 8000::double precision) AND ((location).x &lt;= 12736::double precision) AND ((location).z &gt;= 0::double precision) AND ((location).z &lt;= 100::double precision)) Filter: (((location).y &gt;= 22244::double precision) AND ((location).y &lt;= 25492::double precision) AND (project_id = 4)) -&gt; BitmapAnd (cost=148.55..148.55 rows=9 width=0) -&gt; Bitmap Index Scan on location_x_index (cost=0.00..67.38 rows=2700 width=0) Index Cond: (((location).x &gt;= 8000::double precision) AND ((location).x &lt;= 12736::double precision)) -&gt; Bitmap Index Scan on location_z_index (cost=0.00..80.91 rows=3253 width=0) Index Cond: (((location).z &gt;= 0::double precision) AND ((location).z &lt;= 100::double precision)) -&gt; Hash Join (cost=6566.61..53361.69 rows=211144 width=16) Hash Cond: (treenode_class_instance.class_instance_id = class_instance.id) -&gt; Seq Scan on treenode_class_instance (cost=0.00..25323.79 rows=969285 width=16) Filter: (relation_id = 7828321) -&gt; Hash (cost=5723.54..5723.54 rows=51366 width=8) -&gt; Seq Scan on class_instance (cost=0.00..5723.54 rows=51366 width=8) Filter: (class_id = 7828307) (20 rows) </code></pre> <p>However, if I replace the <code>8000</code> in the <code>x</code> range condition with <code>10644</code>, the query is performed in a fraction of a second and uses this query plan:</p> <pre><code> Limit (cost=58378.94..58378.95 rows=2 width=89) -&gt; Sort (cost=58378.94..58378.95 rows=2 width=89) Sort Key: treenode.parent_id, treenode.id, (((treenode.location).z - 50::double precision)) -&gt; Hash Left Join (cost=57263.11..58378.93 rows=2 width=89) Hash Cond: (treenode.id = treenode_class_instance.treenode_id) -&gt; Bitmap Heap Scan on treenode (cost=231.12..313.44 rows=2 width=81) Recheck Cond: (((location).z &gt;= 0::double precision) AND ((location).z &lt;= 100::double precision) AND ((location).x &gt;= 10644::double precision) AND ((location).x &lt;= 15380::double precision)) Filter: (((location).y &gt;= 22244::double precision) AND ((location).y &lt;= 25492::double precision) AND (project_id = 4)) -&gt; BitmapAnd (cost=231.12..231.12 rows=21 width=0) -&gt; Bitmap Index Scan on location_z_index (cost=0.00..80.91 rows=3253 width=0) Index Cond: (((location).z &gt;= 0::double precision) AND ((location).z &lt;= 100::double precision)) -&gt; Bitmap Index Scan on location_x_index (cost=0.00..149.95 rows=6157 width=0) Index Cond: (((location).x &gt;= 10644::double precision) AND ((location).x &lt;= 15380::double precision)) -&gt; Hash (cost=53361.69..53361.69 rows=211144 width=16) -&gt; Hash Join (cost=6566.61..53361.69 rows=211144 width=16) Hash Cond: (treenode_class_instance.class_instance_id = class_instance.id) -&gt; Seq Scan on treenode_class_instance (cost=0.00..25323.79 rows=969285 width=16) Filter: (relation_id = 7828321) -&gt; Hash (cost=5723.54..5723.54 rows=51366 width=8) -&gt; Seq Scan on class_instance (cost=0.00..5723.54 rows=51366 width=8) Filter: (class_id = 7828307) (21 rows) </code></pre> <p>I'm far from an expert in parsing these query plans, but the clear difference seems to be that with one <code>x</code> range it uses a <code>Hash Left Join</code> for the <code>LEFT OUTER JOIN</code> (which is very fast), while with the other range it uses a <code>Nested Loop Left Join</code> (which seems to be very slow). In both cases the queries return about 90 rows. If I do <code>SET ENABLE_NESTLOOP TO FALSE</code> before the slow version of the query, it goes very fast, but I understand that <a href="https://stackoverflow.com/q/594959/223092">using that setting in general is a bad idea</a>.</p> <p>Can I, for example, create a particular index in order to make it more likely that the query planner will choose the clearly more efficient strategy? Could anyone suggest why PostgreSQL's query planner should be choosing such a poor strategy for one of these queries? Below I have included details of the schema that may be helpful.</p> <hr> <p>The treenode table has 900,000 rows, and is defined as follows:</p> <pre><code> 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 </code></pre> <p>The <code>double3d</code> composite type is defined as follows:</p> <pre><code>Composite type "public.double3d" Column | Type --------+------------------ x | double precision y | double precision z | double precision </code></pre> <p>The other two tables involved in the join are <code>treenode_class_instance</code>:</p> <pre><code> 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 </code></pre> <p>... and <code>class_instance</code>:</p> <pre><code> 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 </code></pre>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload