Skip to content
Commitpedia
Log in Join & edit

Find long-running PostgreSQL queries and slow statements

A psql-based script that lists sessions whose current query or open transaction is older than a threshold, plus pg_stat_statements queries to find the statements that cost the most time overall.

Drafted with AI from official documentation. Scripts were run in a test environment and repository links verified. Awaiting a human check. Did it work for you? Last edited Outdated?

Edit history of Find long-running PostgreSQL queries and slow statements: 1 recorded edit
Edit history · 1 edit
Type
Script
Edits
1
Editors
1
First issued
15 Oct 2026
Last edit
15 Oct 2026
Language
bash
Licence
CC0-1.0

What this changes

Read-only: the script queries pg_stat_activity and prints sessions older than the threshold; nothing is changed. The separate pg_cancel_backend, pg_terminate_backend and CREATE EXTENSION examples do change the server.

How to undo it

Nothing to undo for the script. A cancelled query or terminated session cannot be resumed; the client must run it again. Remove the extension with DROP EXTENSION pg_stat_statements;.

When a PostgreSQL server is slow, the first questions are “what is running right now?” and “what has used the most time recently?”. The pg_stat_activity view answers the first: it has one row per server process with its state, current query and timestamps. The pg_stat_statements extension answers the second.

The script: sessions older than N seconds

#!/usr/bin/env bash
set -Eeuo pipefail

usage() {
  echo "Usage: $(basename "$0") [min-seconds]   (default 60)" >&2
}

MIN_SECONDS=${1:-60}
if ! [[ $MIN_SECONDS =~ ^[0-9]+$ ]]; then
  usage
  exit 2
fi

psql -X -v ON_ERROR_STOP=1 -v min_seconds="$MIN_SECONDS" -P pager=off \
  -d "${PGDATABASE:-postgres}" <<'SQL'
SELECT pid,
       usename,
       datname,
       application_name,
       client_addr,
       state,
       wait_event_type,
       wait_event,
       date_trunc('second', now() - xact_start)  AS xact_age,
       date_trunc('second', now() - query_start) AS query_age,
       left(regexp_replace(query, '\s+', ' ', 'g'), 120) AS query
FROM pg_stat_activity
WHERE backend_type = 'client backend'
  AND pid <> pg_backend_pid()
  AND state IS DISTINCT FROM 'idle'
  AND now() - coalesce(xact_start, query_start) > make_interval(secs => :min_seconds)
ORDER BY coalesce(xact_start, query_start);
SQL

Check before you run this

  • Runs with administrator rights. Make sure you understand every line.
chmod +x pg-long-running.sh
sudo -u postgres ./pg-long-running.sh 300
PGHOST=db1.example.com PGUSER=monitoring ./pg-long-running.sh

It shows client sessions that are not idle and whose transaction (or query, if no transaction is open) started more than the given number of seconds ago, oldest first. To see other users’ query text, run it as a superuser or as a role that has pg_monitor or pg_read_all_stats: GRANT pg_monitor TO monitoring;.

Reading the output

  • state = active: the query is running. If wait_event_type is set too (for example Lock or IO), it is waiting on something.
  • state = idle in transaction: the client opened a transaction and is doing nothing. These hold locks and stop VACUUM from cleaning up; an old xact_age here is often the real problem.
  • xact_age vs. query_age: a long transaction can consist of many short queries.
  • query is truncated to 120 characters here; the full text (up to track_activity_query_size) is in the view.

Who is blocking whom

SELECT pid, pg_blocking_pids(pid) AS blocked_by, wait_event_type, left(query, 60) AS query
FROM pg_stat_activity
WHERE cardinality(pg_blocking_pids(pid)) > 0;

Stopping a query or session

Warning: cancelling rolls back the query’s work, and terminating ends the whole session and rolls back its open transaction. Check the pid, user and query in pg_stat_activity first, and prefer cancel over terminate.

SELECT pg_cancel_backend(12345);
SELECT pg_terminate_backend(12345);

Non-superusers can signal sessions of roles they are a member of, or any non-superuser session if they have pg_signal_backend.

Top statements with pg_stat_statements

pg_stat_statements records execution statistics for every normalized statement (constants replaced by $1, $2…). It must be loaded at server start, so enabling it needs a restart:

# postgresql.conf
shared_preload_libraries = 'pg_stat_statements'
compute_query_id = on
CREATE EXTENSION pg_stat_statements;

SELECT calls,
       round(total_exec_time::numeric, 0) AS total_ms,
       round(mean_exec_time::numeric, 1)  AS mean_ms,
       rows,
       left(regexp_replace(query, '\s+', ' ', 'g'), 100) AS query
FROM pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 15;

SELECT pg_stat_statements_reset();

Sort by total_exec_time to find what costs the most overall (often a fast query called millions of times) and by mean_exec_time to find individually slow statements. Reset the statistics after a change so you can compare before and after. Then run the worst statement with EXPLAIN (ANALYZE, BUFFERS) on a test copy to see why it is slow; remember that EXPLAIN ANALYZE really executes the statement.

Official documentation

Did this work for you?

No votes yet. Be the first.

If it did not work, the report form below tells the editors what went wrong.

See also

  • 0199 psql meta-commands cheat sheet The backslash commands you use every day in psql: connecting, listing databases, tables and roles, describing objects, timing queries, expanded output and… Beginner · Cheat sheet · Any system Article Install & connect · Roles & administration 15 Oct 2026
  • 0200 PostgreSQL roles, privileges and default privileges Create login and group roles, grant access to databases, schemas and tables, and use ALTER DEFAULT PRIVILEGES so tables created later get… Intermediate · Step by step · Linux Article Roles & administration · Security & access 15 Oct 2026
  • 0204 VACUUM, autovacuum and table bloat in PostgreSQL Updates and deletes leave dead row versions behind. VACUUM makes that space reusable, keeps statistics fresh and prevents transaction ID wraparound; autovacuum… Intermediate · Explains an idea · Linux Article Performance & tuning · Roles & administration 15 Oct 2026
  • 0209 Fix: FATAL: password authentication failed for user PostgreSQL reached the server and found a matching pg_hba.conf rule, but the password check failed. Usually a wrong password, a role without… Intermediate · Fixes an error · Any system Article Roles & administration · Security & access 15 Oct 2026
  • 0257 Run PostgreSQL properly on a Linux server: install, lock down, back up, monitor and patch Set up PostgreSQL 18 from the official repository for one application: a dedicated role and database, SCRAM logins from one app server… Intermediate · Guided project · Linux Article Backup & restore · Install & connect · Roles & administration 15 Oct 2026
  • 0326 Fix: too many open files A process hit its file descriptor limit, usually 1024. Raise it for the shell, the systemd unit or the user, fix a… Intermediate · Fixes an error · Linux, macOS Article Performance & tuning · Processes & ports · Services & systemd 15 Oct 2026

History

Every saved edit is kept. The plot on the sleeve draws one line per edit. Open any edit to see exactly what changed.

  1. Dominik +4,258 chars

Report a problem

Something wrong, unsafe or copied? Tell the editors.
What is wrong?