<!-- mobian-agent-page publisher="dailydev" canonical="https://daily.dev/posts/the-sixth-execution-a6umibut5" -->

---
title: The Sixth Execution | daily.dev
description: Prepared statements in PostgreSQL follow a specific rule: for the first five executions, a custom plan is built using the actual parameter values, giving the...
canonical: https://daily.dev/posts/the-sixth-execution-a6umibut5
twitter:card: summary_large_image
twitter:site: @dailydotdev
og:type: website
og:site_name: daily.dev
og:title: The Sixth Execution | daily.dev
og:description: Prepared statements in PostgreSQL follow a specific rule: for the first five executions, a custom plan is built using the actual parameter values, giving the...
og:url: https://daily.dev/posts/the-sixth-execution-a6umibut5
og:image: https://api.daily.dev/og/posts/A6UMIbUt5.png
og:image:alt: The Sixth Execution
og:image:width: 1200
og:image:height: 630
og:locale: en
---

> ## Documentation Index
> Fetch the complete documentation index at: https://daily.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# The Sixth Execution

**[Planet PostgreSQL](https://daily.dev/sources/planet-postgresql)** · 20 min read · 10 upvotes · 5 comments

## Summary

Prepared statements in PostgreSQL follow a specific rule: for the first five executions, a custom plan is built using the actual parameter values, giving the same plan quality as textual substitution. On the sixth execution, PostgreSQL builds a generic plan (no parameter values) and compares its estimated cost to the average custom plan cost; if generic wins, PostgreSQL switches to it and sticks with it until invalidation. Generic plans use blind selectivity estimates (e.g., non-null fraction divided by n_distinct for equality, one-third of the table for inequality), which can be badly wrong on skewed data, causing queries to mysteriously slow down after the sixth execution. The decision is based purely on estimated cost, never actual runtime, and is asymmetrically sticky. Common culprits include PL/pgSQL functions, and drivers like psycopg 3 and JDBC that auto-promote to named statements after a threshold (default 5), causing the switch around the tenth or eleventh execution instead. Fixes include plan_cache_mode=force_custom_plan for skewed queries, driver-level prepare=False, query restructuring, or partial indexes on skewed columns.

## Full article

daily.dev links to this article rather than hosting it. Read it at the original source: <https://postgr.es/p/9t3>

## Questions this post answers

### Why did my PostgreSQL query suddenly get slower after running fine for a while, with no code changes?

PostgreSQL switches a prepared statement from a custom plan to a generic plan starting on its sixth execution, based purely on estimated cost, not actual runtime. If the generic plan's blind cost estimate looks cheaper than the average custom plan cost, PostgreSQL locks in the generic plan for every future execution until something invalidates the cache, even if it performs far worse on real data, especially with skewed column values.

_Debugging a sudden PostgreSQL slowdown? Track down plan-cache gotchas like this one on daily.dev._

### How can I tell if PostgreSQL is using a generic plan instead of a custom plan for my query?

Check for a parameter placeholder like $1 in the EXPLAIN output's Index Cond instead of an actual literal value; a custom plan shows the real value while a generic plan shows $1. The pg_prepared_statements view (PostgreSQL 14+) also reports generic_plans and custom_plans counts per session, and EXPLAIN (GENERIC_PLAN), available in PostgreSQL 16+, shows the generic plan directly without executing anything.

_Diagnosing plan regressions in PostgreSQL is easier with references like this bookmarked on daily.dev._

### How do I force PostgreSQL to always replan a query with real parameter values instead of caching a generic plan?

Set plan_cache_mode to force_custom_plan, a GUC available since PostgreSQL 12 that can be set per session, role, database, or function, causing PostgreSQL to replan with actual values on every execution instead of switching to a cached generic plan after five executions. This is the recommended fix for PL/pgSQL functions that filter on skewed columns, at the cost of a few hundred microseconds of replanning per call.

_Tuning PostgreSQL plan_cache_mode for skewed data? Keep fixes like this handy on daily.dev._

## Community discussion

Top comments from developers on daily.dev.

**@rizzdev** · 0 upvotes

> When the sixth execution builds a generic plan and then a custom one because generic lost, does the generic cost land in the average or only the custom cost?

**@trevorsuna** · 0 upvotes

> The driver threshold is the operational trap here: a query can look healthy in testing, then switch behavior after a few executions under real skew. Tracking custom versus generic plans alongside latency would make this much easier to spot before forcing custom plans globally.

**@agustinbarrientos** · 0 upvotes

> The benchmark should execute the same prepared statement at least seven times with common and rare values so the cached plan has a chance to betray itself

## Similar posts on daily.dev

- [All Your GUCs in a Row: plan\_cache\_mode](https://daily.dev/posts/all-your-gucs-in-a-row-plan-cache-mode-grfcl9ncg) · Planet PostgreSQL · 0 upvotes · 0 comments
- [statements to Find Slow and Expensive Postgres Queries](https://daily.dev/posts/statements-to-find-slow-and-expensive-postgres-queries-ijaz0yhbo) · pganalyze · 0 upvotes · 0 comments
- [AI Execution Plan Analysis: I Gave It My Plan and Asked What Was Wrong](https://daily.dev/posts/ai-execution-plan-analysis-i-gave-it-my-plan-and-asked-what-was-wrong-goqe4azrt) · SQL Authority · 0 upvotes · 0 comments

---

Tags: [#database](https://daily.dev/tags/database), [#postgresql](https://daily.dev/tags/postgresql)

[View this post on daily.dev](https://daily.dev/posts/the-sixth-execution-a6umibut5)

```json
{"@context":"https://schema.org","@graph":[{"@type":"Organization","@id":"https://daily.dev/#organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180},"sameAs":["https://twitter.com/dailydotdev","https://github.com/dailydotdev","https://www.linkedin.com/company/daily-dev-ltd"]},{"@type":"WebSite","@id":"https://daily.dev/#website","url":"https://daily.dev","name":"daily.dev","publisher":{"@id":"https://daily.dev/#organization"},"potentialAction":{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https://daily.dev/search?q={search_term_string}"},"query-input":"required name=search_term_string"}}]}
{"@context":"https://schema.org","@type":"TechArticle","headline":"The Sixth Execution","url":"https://daily.dev/posts/the-sixth-execution-a6umibut5","mainEntityOfPage":{"@type":"WebPage","@id":"https://daily.dev/posts/the-sixth-execution-a6umibut5"},"datePublished":"2026-08-24T20:17:33.534Z","dateModified":"2026-10-15T02:16:13.177Z","description":"Prepared statements in PostgreSQL follow a specific rule: for the first five executions, a custom plan is built using the actual parameter values, giving the...","image":"https://media.daily.dev/image/upload/s--OHB84bZF--/f_auto/v1722860399/public/Placeholder%2010","thumbnailUrl":"https://media.daily.dev/image/upload/s--OHB84bZF--/f_auto/v1722860399/public/Placeholder%2010","isAccessibleForFree":true,"articleSection":"Planet PostgreSQL","inLanguage":"en","publisher":{"@type":"Organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180}},"author":{"@type":"Organization","name":"Planet PostgreSQL","logo":"https://media.daily.dev/image/upload/logos/placeholder.jpg","url":"https://daily.dev/sources/planet-postgresql"},"commentCount":5,"discussionUrl":"https://daily.dev/posts/the-sixth-execution-a6umibut5","interactionStatistic":[{"@type":"InteractionCounter","interactionType":{"@type":"LikeAction"},"userInteractionCount":10},{"@type":"InteractionCounter","interactionType":{"@type":"CommentAction"},"userInteractionCount":5}],"keywords":"database,postgresql","timeRequired":"PT20M"}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://daily.dev"},{"@type":"ListItem","position":2,"name":"Planet PostgreSQL","item":"https://daily.dev/sources/planet-postgresql"},{"@type":"ListItem","position":3,"name":"The Sixth Execution"}]}
{"@context":"https://schema.org","@type":"WebPage","@id":"https://daily.dev/posts/the-sixth-execution-a6umibut5","comment":[{"@type":"Comment","text":"When the sixth execution builds a generic plan and then a custom one because generic lost, does the generic cost land in the average or only the custom cost?","datePublished":"2026-08-25T11:55:47.867Z","url":"https://daily.dev/posts/A6UMIbUt5#c-8c0NIShZ0","author":{"@type":"Person","name":"Andrew","url":"https://daily.dev/rizzdev","image":"https://media.daily.dev/image/upload/s--35vRfXAA--/f_auto/v1785413727/avatars/avatar_fQ7ttCKtPuu6tDvcgQXv5?_a=BAMAMicg0"}},{"@type":"Comment","text":"The driver threshold is the operational trap here: a query can look healthy in testing, then switch behavior after a few executions under real skew. Tracking custom versus generic plans alongside latency would make this much easier to spot before forcing custom plans globally.","datePublished":"2026-08-25T14:49:07.059Z","url":"https://daily.dev/posts/A6UMIbUt5#c-r52egvcnO","author":{"@type":"Person","name":"Trevor Suna","url":"https://daily.dev/trevorsuna","image":"https://media.daily.dev/image/upload/s--dZ7gXxpp--/f_auto/v1784081551/avatars/avatar_EMoP47rpuw8DNjhp6R1b6?_a=BAMAMicg0"}},{"@type":"Comment","text":"The benchmark should execute the same prepared statement at least seven times with common and rare values so the cached plan has a chance to betray itself","datePublished":"2026-08-26T04:05:11.406Z","url":"https://daily.dev/posts/A6UMIbUt5#c-KhFoxkax0","author":{"@type":"Person","name":"Agustin Barrientos","url":"https://daily.dev/agustinbarrientos","image":"https://media.daily.dev/image/upload/s--5ayxQnqn--/f_auto/v1788281802/avatars/avatar_wQYYVe5Tbj0NJ7C7qPoa8?_a=BAMAMicg0"}}]}
{"@context":"https://schema.org","@type":"FAQPage","@id":"https://daily.dev/posts/the-sixth-execution-a6umibut5#faq","mainEntity":[{"@type":"Question","name":"Why did my PostgreSQL query suddenly get slower after running fine for a while, with no code changes?","acceptedAnswer":{"@type":"Answer","text":"PostgreSQL switches a prepared statement from a custom plan to a generic plan starting on its sixth execution, based purely on estimated cost, not actual runtime. If the generic plan's blind cost estimate looks cheaper than the average custom plan cost, PostgreSQL locks in the generic plan for every future execution until something invalidates the cache, even if it performs far worse on real data, especially with skewed column values. Debugging a sudden PostgreSQL slowdown? Track down plan-cache gotchas like this one on daily.dev."}},{"@type":"Question","name":"How can I tell if PostgreSQL is using a generic plan instead of a custom plan for my query?","acceptedAnswer":{"@type":"Answer","text":"Check for a parameter placeholder like $1 in the EXPLAIN output's Index Cond instead of an actual literal value; a custom plan shows the real value while a generic plan shows $1. The pg_prepared_statements view (PostgreSQL 14+) also reports generic_plans and custom_plans counts per session, and EXPLAIN (GENERIC_PLAN), available in PostgreSQL 16+, shows the generic plan directly without executing anything. Diagnosing plan regressions in PostgreSQL is easier with references like this bookmarked on daily.dev."}},{"@type":"Question","name":"How do I force PostgreSQL to always replan a query with real parameter values instead of caching a generic plan?","acceptedAnswer":{"@type":"Answer","text":"Set plan_cache_mode to force_custom_plan, a GUC available since PostgreSQL 12 that can be set per session, role, database, or function, causing PostgreSQL to replan with actual values on every execution instead of switching to a cached generic plan after five executions. This is the recommended fix for PL/pgSQL functions that filter on skewed columns, at the cost of a few hundred microseconds of replanning per call. Tuning PostgreSQL plan_cache_mode for skewed data? Keep fixes like this handy on daily.dev."}}]}
```

