PostgreSQL: команды, лайфхаки и администрирование

Основные команды psql, SQL и администрирование, резервное копирование, роли, производительность и полезные приёмы на одной странице

Подключение

КомандаВариантЧто делает
psqlpsql -h localhost -p 5432 -U app -d appdbПодключение с хостом, портом, пользователем и базой.
psql URIpsql "postgresql://app@db.example.com:5432/appdb?sslmode=require"Строка подключения целиком, с SSL.
psql -cpsql -U postgres -c "SELECT version();"Выполнить одну команду и выйти.
psql -fpsql -d appdb -f migrate.sqlВыполнить SQL-файл.
psql -vpsql -d appdb -v schema=prod -f report.sqlПередать переменную, в скрипте использовать :'schema'.
psql -Atpsql -At -c "SELECT count(*) FROM users;"Чистый вывод без заголовков и рамок, удобно для скриптов.
psql -Xpsql -X -d appdb -f script.sqlНе читать ~/.psqlrc (поведение скриптов предсказуемо).
psql -1psql -1 -d appdb -f migrate.sqlВесь файл в одной транзакции: при ошибке откатится целиком.
psql -qpsql -q -d appdb -f x.sqlТихий режим, без служебных сообщений.

Метакоманды psql

Команды, начинающиеся с обратной косой черты, работают только в psql, а не в SQL.

КомандаВариантЧто делает
\l\l+Список баз данных (с размером и описанием при +).
\c\c appdb appПереключиться на базу и пользователя.
\dt\dt public.*Таблицы в схеме.
\dt+\dt+ ordersТаблицы с размером и описанием.
\d\d ordersСтруктура таблицы: колонки, индексы, ограничения.
\d+\d+ ordersТо же с размером, хранилищем и комментариями.
\di\diИндексы.
\dv / \dm\dv ; \dmПредставления и материализованные представления.
\df\df+ my_funcФункции и их определение.
\dn\dnСхемы.
\du\duРоли и их атрибуты.
\dp\dp ordersПрава доступа к таблице.
\dx\dxУстановленные расширения.
\dT\dT+Пользовательские типы.
\conninfo\conninfoТекущее подключение: база, пользователь, хост, порт, SSL.
\x\x autoРасширенный вывод: широкие строки показываются столбиком.
\timing\timing onПоказывать время выполнения каждого запроса.
\i\i schema.sqlВыполнить файл из текущей сессии.
\o\o result.txtНаправить вывод в файл (\o без аргумента вернуть в терминал).
\copy\copy orders TO 'orders.csv' CSV HEADERКопирование с клиента (не требует прав суперпользователя на сервере).
\watchSELECT count(*) FROM jobs; \watch 5Повторять запрос каждые 5 секунд.
\e\eРедактировать последний запрос во внешнем редакторе.
\s\s history.txtСохранить историю psql в файл.
\set\set ON_ERROR_STOP onОстановить скрипт при первой ошибке (обязательно в миграциях).
\gsetSELECT count(*) AS n FROM users \gsetЗаписать результат запроса в переменную psql.
\q\qВыход.
\?\?Список всех метакоманд.

Базы данных и схема

КомандаВариантЧто делает
CREATE DATABASECREATE DATABASE appdb OWNER app ENCODING 'UTF8';Создать базу с владельцем.
DROP DATABASEDROP DATABASE IF EXISTS appdb;Удалить базу (к ней не должно быть подключений).
CREATE SCHEMACREATE SCHEMA reporting AUTHORIZATION app;Создать схему.
CREATE TABLECREATE TABLE orders (id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY, user_id bigint NOT NULL, total numeric(12,2) NOT NULL DEFAULT 0, created_at timestamptz NOT NULL DEFAULT now());Таблица с идентити-ключом, ограничениями и значениями по умолчанию.
ALTER TABLEALTER TABLE orders ADD COLUMN status text NOT NULL DEFAULT 'new';Добавить колонку (с DEFAULT в PG 11+ не переписывает таблицу).
ALTER TABLE ... ADD CONSTRAINTALTER TABLE orders ADD CONSTRAINT fk_user FOREIGN KEY (user_id) REFERENCES users(id);Внешний ключ.
DROP TABLEDROP TABLE IF EXISTS tmp_import CASCADE;Удалить таблицу и зависимые объекты (CASCADE, осторожно).
TRUNCATETRUNCATE TABLE logs RESTART IDENTITY;Очистить таблицу быстро, сбросив счётчик идентити.
COMMENT ONCOMMENT ON COLUMN orders.total IS 'Сумма в рублях';Комментарий к объекту (виден в \d+).

Индексы

КомандаВариантЧто делает
CREATE INDEXCREATE INDEX idx_orders_user ON orders (user_id);Обычный B-tree индекс. Блокирует запись на время создания.
CREATE INDEX CONCURRENTLYCREATE INDEX CONCURRENTLY idx_orders_user ON orders (user_id);Создание без блокировки записи. Нельзя внутри транзакции.
Частичный индексCREATE INDEX idx_open ON orders (created_at) WHERE status = 'open';Индекс только по нужной части строк, меньше и быстрее.
Индекс по выражениюCREATE INDEX idx_lower_email ON users (lower(email));Для поиска без учёта регистра.
GIN для jsonbCREATE INDEX idx_payload ON events USING gin (payload jsonb_path_ops);Быстрый поиск внутри JSON.
DROP INDEX CONCURRENTLYDROP INDEX CONCURRENTLY idx_old;Удалить индекс без блокировки.
REINDEX CONCURRENTLYREINDEX INDEX CONCURRENTLY idx_orders_user;Перестроить раздутый индекс без блокировки (PG 12+).

Данные: выборки, изменения, транзакции

КомандаВариантЧто делает
INSERT ... RETURNINGINSERT INTO orders (user_id, total) VALUES (1, 990) RETURNING id;Вернуть созданную строку, например её id.
ON CONFLICTINSERT INTO users (email) VALUES ('a@x.ru') ON CONFLICT (email) DO UPDATE SET email = EXCLUDED.email;Upsert: вставить или обновить при конфликте.
ON CONFLICT DO NOTHINGINSERT INTO tags (name) VALUES ('sql') ON CONFLICT DO NOTHING;Пропустить дубликат без ошибки.
UPDATE ... FROMUPDATE orders o SET status = 'paid' FROM payments p WHERE p.order_id = o.id AND p.ok;Обновление по данным из другой таблицы.
DELETE ... USINGDELETE FROM cart c USING users u WHERE c.user_id = u.id AND u.blocked;Удаление с join.
FOR UPDATE SKIP LOCKEDSELECT * FROM jobs WHERE state = 'new' ORDER BY id LIMIT 1 FOR UPDATE SKIP LOCKED;Очередь задач: несколько воркеров не берут одну задачу.
BEGIN / COMMIT / ROLLBACKBEGIN; UPDATE ...; ROLLBACK;Транзакция. Сначала проверяйте изменения, затем фиксируйте.
SAVEPOINTSAVEPOINT s1; ...; ROLLBACK TO SAVEPOINT s1;Откат части транзакции.
COPYCOPY orders FROM '/tmp/orders.csv' CSV HEADER;Массовая загрузка на сервере (нужны права; с клиента используйте \copy).
generate_seriesSELECT generate_series(1, 5);Генерация последовательностей, даты через interval.
EXPLAINEXPLAIN SELECT * FROM orders WHERE user_id = 1;План запроса без выполнения.
EXPLAIN ANALYZEEXPLAIN (ANALYZE, BUFFERS) SELECT ...;План с реальным выполнением и чтением страниц. Для UPDATE и DELETE оборачивайте в транзакцию с откатом.

Роли и права

КомандаВариантЧто делает
CREATE ROLECREATE ROLE app LOGIN PASSWORD 'change-me';Пользователь для приложения (LOGIN даёт право входить).
CREATE ROLE группаCREATE ROLE readonly NOLOGIN;Роль-группа без входа, права выдаются ей.
GRANT рольGRANT readonly TO analyst;Добавить пользователя в роль.
ALTER ROLEALTER ROLE app PASSWORD 'new';Сменить пароль или атрибуты.
ALTER ROLE VALID UNTILALTER ROLE analyst VALID UNTIL '2027-01-01';Срок действия учётной записи.
GRANT CONNECTGRANT CONNECT ON DATABASE appdb TO readonly;Право подключаться к базе.
GRANT USAGEGRANT USAGE ON SCHEMA public TO readonly;Доступ к схеме (без него таблицы не видны).
GRANT SELECT ON ALL TABLESGRANT SELECT ON ALL TABLES IN SCHEMA public TO readonly;Чтение всех существующих таблиц схемы.
ALTER DEFAULT PRIVILEGESALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO readonly;Права на таблицы, которые появятся в будущем.
REVOKEREVOKE ALL ON orders FROM readonly;Забрать права.
DROP ROLEDROP OWNED BY old_user; DROP ROLE old_user;Удалить роль: сначала передать или удалить её объекты.
\du\du+ appПроверка атрибутов роли в psql.

Резервное копирование и восстановление

КомандаВариантЧто делает
pg_dump -Fcpg_dump -Fc -f appdb.dump appdbБэкап в собственном сжатом формате (рекомендуемый).
pg_dump -Fd -jpg_dump -Fd -j 4 -f appdb_dir appdbКаталог-формат, параллельный дамп на 4 процесса.
pg_dump -Fppg_dump -Fp appdb > appdb.sqlОбычный SQL-скрипт (читаемый, но большой).
pg_dump -tpg_dump -Fc -t orders -f orders.dump appdbОдна таблица.
pg_dump -npg_dump -Fc -n reporting -f reporting.dump appdbОдна схема.
pg_dump --schema-onlypg_dump --schema-only -f schema.sql appdbТолько структура без данных.
pg_dump --data-onlypg_dump --data-only -Fc -f data.dump appdbТолько данные.
pg_dumpall --globals-onlypg_dumpall --globals-only -f roles.sqlРоли и табличные пространства, которых нет в обычном дампе базы.
pg_restorepg_restore -d appdb_new -j 4 appdb.dumpВосстановление из формата -Fc или -Fd в существующую базу.
pg_restore --cleanpg_restore --clean --if-exists -d appdb appdb.dumpСначала удалить объекты, потом создать заново.
pg_restore -lpg_restore -l appdb.dumpОглавление дампа без восстановления.
psql -f (SQL-дамп)psql -d appdb_new -f appdb.sqlВосстановление обычного SQL-дампа.
CREATE DATABASE TEMPLATECREATE DATABASE test_copy TEMPLATE appdb;Быстрая копия базы (к исходной не должно быть подключений).

Проверка резервной копии

# Восстановить в тестовую базу и проверить количество строк
createdb appdb_check
pg_restore -d appdb_check -j 4 appdb.dump
psql -d appdb_check -At -c "SELECT count(*) FROM orders;"
dropdb appdb_check

Производительность и диагностика

ЗадачаSQL
Активные запросы и их длительностьSELECT pid, usename, state, now() - query_start AS duration, left(query, 80) FROM pg_stat_activity WHERE state <> 'idle' ORDER BY duration DESC;
Завершить запрос мягкоSELECT pg_cancel_backend(12345);
Разорвать сессиюSELECT pg_terminate_backend(12345);
Кто кого блокируетSELECT pid, pg_blocking_pids(pid) AS blocked_by, left(query, 80) FROM pg_stat_activity WHERE cardinality(pg_blocking_pids(pid)) > 0;
Размер базыSELECT pg_size_pretty(pg_database_size('appdb'));
Самые большие таблицыSELECT relname, pg_size_pretty(pg_total_relation_size(relid)) FROM pg_stat_user_tables ORDER BY pg_total_relation_size(relid) DESC LIMIT 10;
Неиспользуемые индексыSELECT relname, indexrelname, idx_scan FROM pg_stat_user_indexes WHERE idx_scan = 0 ORDER BY relname;
Обновить статистикуANALYZE orders;
Очистка мёртвых строкVACUUM (VERBOSE, ANALYZE) orders;
Кеш-попаданияSELECT sum(blks_hit)::float / nullif(sum(blks_hit + blks_read), 0) FROM pg_stat_database;
Самые медленные запросыSELECT calls, mean_exec_time, left(query, 80) FROM pg_stat_statements ORDER BY total_exec_time DESC LIMIT 10;

Для последнего запроса нужно расширение pg_stat_statements: добавьте его в shared_preload_libraries, перезапустите сервер и выполните CREATE EXTENSION pg_stat_statements;.

Настройки сервера

КомандаВариантЧто делает
SHOWSHOW work_mem;Текущее значение параметра.
SHOW config_fileSHOW config_file;Путь к postgresql.conf.
SHOW ALLSHOW ALL;Все параметры и их источники.
pg_settingsSELECT name, setting, source FROM pg_settings WHERE source <> 'default';Что переопределено вручную.
ALTER SYSTEMALTER SYSTEM SET work_mem = '64MB';Записать в postgresql.auto.conf.
pg_reload_confSELECT pg_reload_conf();Применить параметры, которые не требуют перезапуска.
SET (сессия)SET statement_timeout = '30s';Параметр только для текущей сессии.
pg_hba.confhost appdb app 10.0.0.0/24 scram-sha-256Кто, откуда и каким способом может подключиться.

Полезные параметры

ПараметрДля чего
statement_timeoutУбивать запрос, который идёт дольше заданного времени.
lock_timeoutНе ждать блокировку дольше заданного времени (полезно перед миграциями).
idle_in_transaction_session_timeoutРазрывать сессии, которые держат открытую транзакцию без работы.
work_memПамять на операции сортировки и хеширования в одном узле запроса.
maintenance_work_memПамять для VACUUM и CREATE INDEX.
log_min_duration_statementЛогировать запросы дольше заданного времени (например, 500 мс).
shared_buffersОбщий кеш страниц сервера (обычно около четверти RAM).

Лайфхаки

  1. Пароль без истории. Вместо PGPASSWORD в команде используйте файл ~/.pgpass в формате host:port:db:user:password с правами chmod 600.
  2. Проверить миграцию в транзакции. Оберните скрипт в BEGIN; и в конце выполните ROLLBACK;, пока не убедитесь, что всё работает. Или используйте psql -1.
  3. Остановиться при ошибке. В начале скрипта: \set ON_ERROR_STOP on. Иначе psql продолжит выполнять после сбоя.
  4. Удобная запись в .psqlrc. Строка \x auto и \timing on в ~/.psqlrc сразу делают вывод читаемее и показывают время запросов.
  5. Выгрузить CSV с клиента. \copy (SELECT ...) TO 'out.csv' CSV HEADER. Запрос в скобках, а не просто имя таблицы.
  6. Быстрая проверка количества без блокировок. Для больших таблиц точный count(*) медленный. Оценка: SELECT reltuples::bigint FROM pg_class WHERE relname = 'orders';.
  7. Идентификаторы без лишних расширений. Начиная с PostgreSQL 13, gen_random_uuid() встроен в ядро.
  8. Пакетная вставка. Одна инструкция INSERT ... VALUES (...), (...), (...) на тысячи строк быстрее тысяч отдельных INSERT.
  9. Очередь без дублей. FOR UPDATE SKIP LOCKED позволяет нескольким воркерам брать задачи параллельно.
  10. Порог для сложных запросов. Если EXPLAIN ANALYZE показывает Seq Scan по большой таблице с редким условием, проверьте индекс и актуальность статистики через ANALYZE.
  11. Сохранить вывод команды в переменную shell. COUNT=$(psql -At -d appdb -c "SELECT count(*) FROM orders;").
  12. Список подключений по приложениям. SELECT application_name, count(*) FROM pg_stat_activity GROUP BY 1;. Задайте имя приложения в строке подключения, чтобы его было видно.
  13. Не используйте OFFSET для глубокой пагинации. При больших смещениях запрос читает и отбрасывает строки. Используйте курсорную пагинацию по индексированному ключу: WHERE id > :last_id ORDER BY id LIMIT 50.
  14. Округление времени. date_trunc('day', created_at) группирует по суткам, date_trunc('hour', ...) по часам.

Работа с JSON

-- Значение по ключу как текст
SELECT payload->>'email' FROM events;

-- Вложенный путь
SELECT payload#>>'{address,city}' FROM events;

-- Фильтр по содержимому (с GIN-индексом)
SELECT * FROM events WHERE payload @> '{"type": "signup"}';

-- Развернуть массив в строки
SELECT jsonb_array_elements_text(tags) FROM posts;

Типичные задачи одной командой

# Создать пользователя и базу для приложения
psql -U postgres -c "CREATE ROLE app LOGIN PASSWORD 'secret';"
psql -U postgres -c "CREATE DATABASE appdb OWNER app;"

# Ежедневный бэкап с датой и проверкой
pg_dump -Fc -f /backup/appdb-$(date +%F).dump appdb && echo "backup ok"

# Сколько активных подключений и лимит
psql -At -U postgres -c "SELECT count(*) FROM pg_stat_activity;" \
  -c "SHOW max_connections;"

# Выполнить SQL из файла с выводом времени
psql -d appdb -v ON_ERROR_STOP=1 -c "\timing on" -f report.sql
DROP DATABASE, DROP TABLE ... CASCADE, TRUNCATE и DELETE без WHERE необратимы. Перед опасными операциями сделайте бэкап, выполните в транзакции и проверьте результат. Команды pg_terminate_backend прерывают чужие сессии: сначала пробуйте pg_cancel_backend.
Миграции схемы лучше выполнять с SET lock_timeout = '5s';. Тогда если таблица занята долгой транзакцией, миграция упадёт с ошибкой, а не встанет в очередь и не заблокирует все запросы приложения.