Первая версия «Змейки» хранит рекорд в браузере — в localStorage. Работает это ровно до того момента, пока игрок не откроет игру с телефона или не почистит историю: рекорд исчезает, а таблица лидеров у каждого своя. Как только приложению нужны общие данные для всех пользователей, появляется вопрос настоящей базы данных. Чаще всего ответом становится PostgreSQL — и эта статья про то, как он устроен, как его поставить и как подключить к своему проекту.
Обзорное сравнение разных хранилищ есть в отдельной статье — базы данных для новичка. Здесь мы идём вглубь одного инструмента: таблицы, типы, установка, строка подключения, SQL-запросы, копии и грабли.
Содержание
- Что такое PostgreSQL простыми словами
- Чем PostgreSQL отличается от SQLite и облачных решений
- Как устроены таблицы PostgreSQL: строки, типы данных и связи
- Установка PostgreSQL на Windows, macOS и Linux
- Как подключить базу данных PostgreSQL к приложению
- Базовые операции с данными на SQL
- Резервные копии и восстановление
- Типичные ошибки новичка
- Частые вопросы
- Заключение
- Источники
Что такое PostgreSQL простыми словами
PostgreSQL (произносится «постгрес-кью-эль», в разговоре просто «постгрес») — система управления базами данных. Программа-сервер, которая принимает от твоего приложения запросы на понятном ей языке SQL, аккуратно хранит данные на диске и отдаёт их обратно.
Аналогия с офисом работает почти буквально. Таблица — папка с однотипными бланками: в одной анкеты игроков, в другой протоколы сыгранных партий. База — шкаф целиком, где эти папки стоят. А PostgreSQL — сотрудник с единственным ключом от шкафа: ты не роешься в бумагах сам, а просишь его — «дай десять лучших результатов за неделю», «добавь новую анкету». Он выполняет просьбу и не даёт двум людям одновременно испортить один документ.
Такой посредник решает четыре задачи, которые в файлах решать мучительно:
- Одновременный доступ. В игру играют сто человек сразу, все пишут рекорды. Сервер выстраивает эти обращения так, чтобы записи не затирали друг друга.
- Целостность. База не даст сохранить результат игрока, которого не существует, и не пропустит текст в поле, где должно быть число.
- Быстрый поиск. Найти десять лучших результатов среди миллиона строк она умеет за миллисекунды благодаря индексам.
- Надёжность. Если сервер выключат посреди записи, при следующем запуске база восстановит согласованное состояние, а не оставит половину операции.
PostgreSQL — свободный проект с открытым исходным кодом, ему больше тридцати лет, и развивает его международное сообщество, а не одна компания. Отсюда два следствия: платить за саму базу не нужно (платишь за сервер или облачный сервис), и «версии для бедных» с урезанными возможностями тут нет — все функции доступны сразу.
Ещё одно свойство, из-за которого его любят: расширяемость. К базе подключаются дополнения с новыми типами данных и умениями — географические координаты, полнотекстовый поиск по-русски, векторы для ИИ-функций. Новичку они не нужны, но приятно знать, что база не упрётся в потолок, когда проект вырастет.
Главная мысль: PostgreSQL — это не файл и не таблица в Excel, а отдельная программа-сервер. Приложение с ней разговаривает по сети, даже если оба живут на одном компьютере.
Чем PostgreSQL отличается от SQLite и облачных решений
Сравнение хранилищ по задачам разобрано в статье базы данных для новичка, поэтому здесь — только то, что важно понимать про сам PostgreSQL и его окружение.
SQLite — библиотека, PostgreSQL — сервер
SQLite тоже понимает SQL и тоже хранит таблицы, но устроен принципиально иначе: это не отдельная программа, а библиотека внутри твоего приложения, а вся база — один файл на диске. Никого не нужно запускать, ничего не нужно настраивать, порта нет.
Отсюда и разделение ролей. SQLite прекрасен, когда база обслуживает одно приложение на одном устройстве: мобильные и десктопные программы, локальные прототипы. PostgreSQL нужен, когда к одним данным по сети обращаются много клиентов — то есть в любом веб-приложении с общей таблицей рекордов и аккаунтами.
| SQLite | PostgreSQL | |
|---|---|---|
| Что это | Библиотека, база — один файл | Отдельный сервер, слушает порт |
| Запуск | Не нужен | Служба на компьютере или сервере |
| Много одновременных записей | Плохо, запись блокирует файл | Штатный режим работы |
| Доступ по сети | Нет | Да |
| Типичное применение | Прототип, мобильное приложение | Веб-приложение с пользователями |
Supabase — это PostgreSQL внутри
Момент, который экономит новичку кучу времени: Supabase — не альтернатива PostgreSQL, а обёртка вокруг него. Заводя проект в Supabase, ты получаешь настоящую базу данных PostgreSQL, развёрнутую в облаке, плюс готовый слой сервисов сверху: авторизацию пользователей, автоматический REST-интерфейс к таблицам, хранилище файлов, веб-редактор таблиц и SQL-консоль в браузере.
Знание SQL и устройства таблиц при переходе между ними не пропадает: запрос из редактора Supabase дословно работает в локальном PostgreSQL и наоборот. Меняется только то, кто отвечает за установку, обновления и резервные копии.
Из этого же вытекает особенность безопасности: раз база настоящая и доступна из браузера через публичный интерфейс, доступ к строкам ограничивают политиками на уровне самой базы — механизм называется Row Level Security. Ему посвящена отдельная статья про политики RLS в Supabase. В локальном PostgreSQL этот механизм тоже есть, просто там он нужен реже: до базы обычно достаёт только твой сервер, а не браузер игрока.
Когда что выбирать в учебном проекте
Для «Змейки» порядок такой. Пока игра работает офлайн и рекорд личный — хватает localStorage. Появилась общая таблица лидеров — берёшь Supabase: администрировать сервер не надо, а PostgreSQL под капотом тот же. Локальный PostgreSQL ставят, когда хочется разобраться в устройстве, работать без интернета или отлаживать запросы, ничего не ломая в боевой базе.
Сравнение Supabase с другим популярным облачным вариантом — в статье Firebase против Supabase.
Как устроены таблицы PostgreSQL: строки, типы данных и связи
Внутри база данных PostgreSQL — набор таблиц. Таблица похожа на лист в Excel, но с жёсткими правилами: колонки заданы заранее, у каждой свой тип, и нарушить его нельзя.
Таблица, строка, колонка
Возьмём «Змейку». Нам нужны две таблицы: игроки и их результаты.
Таблица players — по строке на игрока:
| id | nickname | created_at |
|---|---|---|
| 1 | zmeelov | 2026-09-01 12:04:11 |
| 2 | anna_k | 2026-09-01 18:22:40 |
Таблица scores — по строке на сыгранную партию:
| id | player_id | score | played_at |
|---|---|---|---|
| 1 | 1 | 340 | 2026-09-01 12:31:00 |
| 2 | 2 | 512 | 2026-09-02 09:15:12 |
| 3 | 1 | 780 | 2026-09-02 20:41:55 |
Колонка задаёт, что за величина хранится, строка — один конкретный факт: один игрок, одна партия. Правило, которое стоит усвоить сразу: одна строка описывает один объект, и колонки не смешивают разные сущности. Хранить ник игрока в каждой строке результатов — плохая идея: сменит человек ник, и придётся править сотню строк вместо одной.
Типы данных
Тип — обещание базе, какие значения окажутся в колонке. Оно проверяется на каждой записи: попытка положить слово в числовую колонку закончится ошибкой, а не тихой порчей данных. Основные типы, которых новичку хватает надолго:
| Тип | Что хранит | Пример из «Змейки» |
|---|---|---|
integer |
Целое число | score — набранные очки |
bigint |
Большое целое | счётчик просмотров, id в крупной таблице |
numeric |
Точное дробное | цена, если появится магазин скинов |
text |
Строка любой длины | nickname |
boolean |
Да или нет | is_banned |
timestamptz |
Дата и время с часовым поясом | played_at |
uuid |
Длинный уникальный идентификатор | user_id из системы аккаунтов |
jsonb |
Произвольная структура в формате JSON | настройки игрока |
Пара практических замечаний. Для строк в PostgreSQL смело бери text — он не медленнее, чем varchar с ограничением длины, а ограничивать длину лучше отдельной проверкой, если она действительно нужна. Для времени бери timestamptz, а не timestamp: он хранит момент в универсальном времени и корректно показывает его игрокам из разных часовых поясов, тогда как обычный timestamp хранит «просто цифры» и рано или поздно приводит к путанице со временем рекорда.
Тип jsonb соблазняет складывать в него всё подряд: структуру заранее продумывать не надо. Работает это до первого отчёта, когда выяснится, что считать по обычным колонкам на порядок проще. Компромисс: то, по чему ищешь и сортируешь, — отдельными колонками, редкие необязательные настройки — в jsonb.
Первичный ключ
Первичный ключ — колонка, которая однозначно определяет строку. Обычно её называют id, и база заполняет её сама, выдавая каждой новой строке следующий номер. Без ключа нельзя надёжно сказать «обнови вот эту строку»: если у двух игроков совпадут ники, обновятся оба.
Создание таблицы игроков выглядит так:
CREATE TABLE players (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
nickname text NOT NULL UNIQUE,
created_at timestamptz NOT NULL DEFAULT now()
);
Разберём построчно. bigint GENERATED ALWAYS AS IDENTITY — целое число, которое база выдаёт сама по возрастанию. PRIMARY KEY объявляет колонку первичным ключом, NOT NULL запрещает пустое поле, UNIQUE не даст завести двух игроков с одинаковым ником, DEFAULT now() подставит текущее время, если приложение его не передало.
Связи между таблицами и внешний ключ
Теперь таблица результатов. Каждый результат принадлежит игроку, и эту принадлежность выражают внешним ключом — колонкой, которая ссылается на первичный ключ другой таблицы:
CREATE TABLE scores (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
player_id bigint NOT NULL REFERENCES players(id) ON DELETE CASCADE,
score integer NOT NULL CHECK (score >= 0),
played_at timestamptz NOT NULL DEFAULT now()
);
REFERENCES players(id) заставляет базу проверять: игрок с таким номером существует. Записать результат несуществующему игроку она откажется — это и называется целостностью данных. ON DELETE CASCADE описывает, что делать при удалении игрока: удалить заодно все его партии, чтобы не оставлять висящие строки. CHECK (score >= 0) — простая проверка на уровне базы: отрицательных очков не бывает, и никакая ошибка в коде приложения их сюда не пропишет.
Связь «один игрок — много результатов» называют «один ко многим», и это самый частый вид связи. Есть ещё «многие ко многим» — например, если у игры появятся достижения: одно достижение получают многие игроки, у одного игрока много достижений. Такую связь выражают третьей, связующей таблицей, где в каждой строке пара «игрок — достижение».
Индексы
Индекс — вспомогательная структура, благодаря которой база находит нужные строки, не просматривая всю таблицу. Работает как алфавитный указатель в книге: вместо перелистывания страниц ты открываешь нужную сразу.
Таблице лидеров нужен индекс по очкам, а списку партий конкретного игрока — по номеру игрока:
CREATE INDEX scores_score_idx ON scores (score DESC);
CREATE INDEX scores_player_idx ON scores (player_id);
По первичным ключам и колонкам с UNIQUE индексы создаются автоматически, отдельно их писать не нужно. Не стоит и вешать индекс на каждую колонку про запас: каждый индекс занимает место и слегка замедляет запись, потому что при каждой вставке база обновляет ещё и его. Разумный подход — добавлять индекс, когда конкретный запрос стал медленным, а не заранее.
Схема и как посмотреть данные PostgreSQL глазами
Таблицы живут внутри схемы — именованной группы объектов. По умолчанию используется схема public, и на старте о схемах можно не думать: они пригодятся, когда захочется отделить служебные таблицы от пользовательских.
Посмотреть, что внутри базы, можно тремя способами: консольной утилитой psql (\dt покажет список таблиц, \d scores — устройство конкретной), графическими программами вроде pgAdmin или DBeaver и веб-редактором, если база живёт в облаке.
Установка PostgreSQL на Windows, macOS и Linux
Установка PostgreSQL сводится к трём шагам независимо от системы: поставить сервер, задать пароль главного пользователя, проверить подключение. Дистрибутивы и актуальные версии бери на официальном сайте — postgresql.org/download. Точные названия пунктов установщика тут приводить не буду: они меняются от версии к версии, а логика остаётся одинаковой.
Установка PostgreSQL на Windows
Для Windows на официальном сайте есть графический установщик. Скачиваешь его, запускаешь и проходишь мастер, который по пути спросит четыре вещи:
- Куда ставить — годится путь по умолчанию.
- Какие компоненты ставить — нужны сам сервер и командная строка; pgAdmin (графический клиент) полезен новичку, стоит оставить.
- Пароль для пользователя
postgres— главный административный доступ к базе. Придумай надёжный и сразу сохрани его в менеджере паролей: восстановить забытый пароль сложнее, чем задать его сейчас. - Порт — по умолчанию 5432, менять его без причины не нужно.
После установки PostgreSQL Windows запускает сервер как службу: он стартует вместе с системой и работает в фоне, никакого окна у него нет. Если база вдруг перестала отвечать, службу видно в оснастке «Службы» — там же её можно перезапустить.
Проверка, что всё встало, — в командной строке:
psql -U postgres -h localhost -p 5432
Утилита спросит пароль и, если он верный, покажет приглашение postgres=#. Это и есть консоль базы. Выход — команда \q.
Если Windows отвечает, что psql не является внутренней или внешней командой, значит путь к папке bin установленного PostgreSQL не попал в переменную среды PATH. Два выхода: добавить путь в переменные среды через параметры системы либо запускать psql из ярлыка SQL Shell, который установщик кладёт в меню «Пуск».
macOS
На macOS два удобных пути. Первый — Homebrew, менеджер пакетов для терминала:
brew install postgresql@17
brew services start postgresql@17
Номер версии подставь актуальный на момент установки — его видно на сайте проекта. Второй путь — приложение Postgres.app: сервер запускается и останавливается кнопкой, новичку так проще.
При установке через Homebrew пользователь базы обычно совпадает с именем пользователя системы, и подключиться можно короткой командой:
psql postgres
Linux
В дистрибутивах на основе Debian и Ubuntu PostgreSQL ставится из штатного репозитория:
sudo apt update
sudo apt install postgresql postgresql-contrib
sudo systemctl status postgresql
Пакет postgresql-contrib добавляет набор стандартных расширений, которые часто пригождаются. Сервер после установки запускается автоматически и настроен так, что администратор системы может войти под пользователем postgres без пароля:
sudo -u postgres psql
Первым делом задай пароль этому пользователю — он понадобится для подключения из приложения:
ALTER USER postgres WITH PASSWORD 'сюда_надёжный_пароль';
Отдельная база и отдельный пользователь под проект
Работать из-под postgres во всех проектах — привычка вредная: этот пользователь может в базе всё, включая удаление чужих данных. Правильнее завести под каждый проект свою базу и своего пользователя с доступом только к ней:
CREATE DATABASE snake;
CREATE USER snake_app WITH PASSWORD 'отдельный_пароль_приложения';
GRANT ALL PRIVILEGES ON DATABASE snake TO snake_app;
Дальше подключаешься к новой базе и отдаёшь права на схему, иначе приложение не сможет создавать в ней таблицы:
\c snake
GRANT ALL ON SCHEMA public TO snake_app;
Теперь у «Змейки» отдельная песочница: даже если пароль приложения утечёт, остальные базы на этом сервере не пострадают.
Как подключить базу данных PostgreSQL к приложению
Приложение общается с базой не напрямую, а через драйвер — библиотеку для твоего языка, которая умеет говорить по протоколу PostgreSQL. В Node.js это обычно pg, в Python — psycopg, и так далее. Общая схема одинаковая: приложение открывает соединение, отправляет запрос, получает строки.
flowchart TB
A["Браузер игрока"] --> B["Приложение на сервере"]
B --> C["Драйвер и пул соединений"]
C --> D["PostgreSQL, порт 5432"]
D --> E["Таблица players"]
D --> F["Таблица scores"]
Между приложением и базой стоит ещё один слой — HTTP-интерфейс, через который браузер отправляет запросы на сервер. Что это такое и почему браузер никогда не ходит в базу напрямую, разобрано в статье что такое API.
Строка подключения
Все параметры соединения принято складывать в одну строку — connection string. Выглядит она так:
postgresql://snake_app:пароль@localhost:5432/snake
Читается слева направо: протокол, имя пользователя, пароль, адрес сервера, порт, имя базы. Для базы на своём компьютере адрес localhost, для облачной — адрес, который выдал сервис. Облачные базы почти всегда требуют шифрования, и тогда в конец добавляют параметр ?sslmode=require.
Переменные окружения — и никаких паролей в коде
Строку подключения нельзя писать прямо в коде. Код уезжает в репозиторий на GitHub, и вместе с ним туда уедет пароль от базы — а публичные репозитории роботы сканируют на утёкшие ключи круглосуточно.
Хранят такие значения в переменных окружения. Локально их кладут в файл .env в корне проекта:
DATABASE_URL=postgresql://snake_app:пароль@localhost:5432/snake
Файл .env обязательно добавляют в .gitignore, чтобы он не попал в репозиторий:
echo ".env" >> .gitignore
На боевом сервере или хостинге те же переменные задают через панель управления — там для этого есть отдельный раздел. Код при этом не меняется: он в обоих случаях читает DATABASE_URL из окружения.
Рядом полезно держать .env.example — те же имена переменных без значений. Он коммитится в репозиторий и служит подсказкой, какие настройки нужны проекту.
Пул соединений
Открыть соединение с базой — операция небыстрая: нужно установить сетевой канал, проверить пароль, выделить процесс на стороне сервера. Делать это на каждый запрос игрока расточительно, а если игроков много — сервер упрётся в лимит одновременных соединений и начнёт отказывать.
Решение — пул: заранее открытый набор соединений, которые приложение переиспользует. Запрос берёт свободное соединение, выполняется, возвращает его обратно в пул. Драйверы умеют это из коробки:
import { Pool } from 'pg';
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
max: 10,
idleTimeoutMillis: 30000,
});
const { rows } = await pool.query(
'SELECT nickname, score FROM scores JOIN players ON players.id = scores.player_id ORDER BY score DESC LIMIT 10'
);
Параметр max задаёт размер пула. Больше — не значит лучше: у сервера базы есть свой предел одновременных соединений, и десяток экземпляров приложения с пулом по сто соединений его переберут. Для учебного проекта пяти–десяти достаточно.
Отдельный случай — бессерверные платформы, где каждый запрос выполняется в новом коротком окружении и соединения не переживают между вызовами. Там используют внешний пулер: у облачных провайдеров это обычно отдельный адрес подключения для бессерверных приложений.
Параметры вместо склейки строк
Главное правило безопасности при работе с базой: значения передаются отдельными параметрами, а не вклеиваются в текст запроса.
// правильно: значение приходит отдельным параметром
await pool.query('INSERT INTO scores (player_id, score) VALUES ($1, $2)', [playerId, score]);
// опасно: игрок может подставить в ник кусок SQL
await pool.query(`INSERT INTO scores (player_id, score) VALUES (${playerId}, ${score})`);
Вторая форма открывает дорогу SQL-инъекции: злоумышленник вводит в поле ввода не ник, а фрагмент запроса, и база честно его выполняет — вплоть до удаления таблицы. Плейсхолдеры $1, $2 эту возможность закрывают: база рассматривает переданное как значение, а не как команду.
Проверка подключения
Прежде чем искать ошибку в коде, проверь, что база вообще отвечает — тем же psql с той же строкой подключения:
psql "postgresql://snake_app:пароль@localhost:5432/snake" -c "SELECT version();"
Ответ с версией сервера означает: база жива, пароль верный, доступ есть, проблема в приложении. Ошибка — проблема в самой связке, и дальше её ищут по разделу про типичные ошибки ниже.
Базовые операции с данными на SQL
SQL — язык запросов к базе. Четыре команды покрывают почти всё, что делает обычное приложение: SELECT читает, INSERT добавляет, UPDATE изменяет, DELETE удаляет. Разберём их на данных «Змейки».
SELECT — чтение
Простейший запрос — все строки таблицы:
SELECT * FROM players;
Звёздочка означает «все колонки». В приложении так лучше не писать: перечисляй нужные колонки явно, тогда запрос не сломается при добавлении новой и не потянет по сети лишнее.
Фильтрация — через WHERE:
SELECT nickname, created_at
FROM players
WHERE created_at > now() - interval '7 days';
Сортировка и ограничение количества — ORDER BY и LIMIT. Так выглядит таблица лидеров:
SELECT p.nickname, s.score, s.played_at
FROM scores AS s
JOIN players AS p ON p.id = s.player_id
ORDER BY s.score DESC
LIMIT 10;
JOIN соединяет две таблицы по условию: к каждой строке результата подставляется ник игрока из таблицы players. Псевдонимы s и p сокращают запись, когда таблиц несколько.
Агрегация — подсчёт итогов по группам:
SELECT p.nickname,
COUNT(*) AS games,
MAX(s.score) AS best,
ROUND(AVG(s.score)) AS average
FROM scores AS s
JOIN players AS p ON p.id = s.player_id
GROUP BY p.nickname
ORDER BY best DESC;
GROUP BY собирает строки в группы по нику, а функции COUNT, MAX, AVG считают по каждой группе: сколько партий, лучший результат, средний. Такой запрос — готовая статистика профиля игрока.
INSERT — добавление
INSERT INTO players (nickname) VALUES ('zmeelov');
Колонки id и created_at не указаны намеренно: их заполнит база. Часто нужно сразу узнать номер созданной строки — для этого есть RETURNING:
INSERT INTO scores (player_id, score)
VALUES (1, 780)
RETURNING id, played_at;
Запрос вернёт номер записи и время, как будто ты следом сделал SELECT, но за один заход.
Ещё одна полезная форма — вставка «если такого ещё нет». Игрок заходит в игру, и приложение не знает, новый он или нет:
INSERT INTO players (nickname)
VALUES ('anna_k')
ON CONFLICT (nickname) DO NOTHING
RETURNING id;
Без ON CONFLICT повторная вставка нарушила бы условие UNIQUE и вернула ошибку.
UPDATE — изменение
UPDATE players
SET nickname = 'anna_kk'
WHERE id = 2;
Смертельно опасная деталь: UPDATE без WHERE изменит все строки таблицы. Одна забытая строка условия — и у всех игроков одинаковый ник. Привычка, которая спасает: сначала напиши тот же запрос как SELECT с тем же WHERE, посмотри, сколько строк он вернул, и только потом меняй SELECT на UPDATE.
DELETE — удаление
DELETE FROM scores
WHERE played_at < now() - interval '1 year';
То же правило про WHERE действует и здесь, только цена ошибки выше: удалённые строки не вернуть без резервной копии. Для данных, которые могут понадобиться позже, часто используют мягкое удаление — колонку deleted_at, куда ставится время, а строка физически остаётся в таблице.
Транзакции
Транзакция — несколько операций, которые выполняются как одна: либо все, либо ни одной. Пример из игры с магазином скинов: списать монеты и выдать скин. Упади сервер между этими действиями — игрок останется без монет и без скина.
BEGIN;
UPDATE players SET coins = coins - 100 WHERE id = 1;
INSERT INTO player_skins (player_id, skin_id) VALUES (1, 5);
COMMIT;
Между BEGIN и COMMIT изменения видны только твоему соединению. COMMIT фиксирует их для всех, ROLLBACK отменяет целиком. Если в середине произойдёт ошибка, база откатит транзакцию сама, и половинчатого состояния не возникнет.
Как это выглядит в приложении
Тот же запрос из кода на Node.js:
async function saveScore(playerId, score) {
const { rows } = await pool.query(
'INSERT INTO scores (player_id, score) VALUES ($1, $2) RETURNING id, played_at',
[playerId, score]
);
return rows[0];
}
Тексты запросов те же, что ты отлаживал в psql — в этом и удобство: сначала добиваешься правильного результата в консоли, потом переносишь готовый запрос в код.
Миграции
Структура таблиц меняется по ходу проекта: добавилась колонка, появился индекс. Выполнять ALTER TABLE руками на боевой базе — путь к рассинхрону, когда у тебя на компьютере одна структура, а на сервере другая. Правильный подход — миграции: пронумерованные файлы с SQL-командами, которые лежат в репозитории рядом с кодом и применяются по порядку. Инструменты есть под каждый язык, суть у всех одна: структура базы описана в файлах и повторяема на любой машине.
-- 002_add_country_to_players.sql
ALTER TABLE players ADD COLUMN country text;
Резервные копии и восстановление
База данных — единственная часть приложения, которую нельзя пересобрать заново. Код лежит в репозитории, картинки можно нарисовать снова, а данные игроков существуют в одном экземпляре. Поэтому копии делают до того, как они понадобятся.
Снятие копии
Штатный инструмент — утилита pg_dump. Она подключается к базе и выгружает её содержимое в файл:
pg_dump -U snake_app -h localhost -d snake -F c -f snake-2026-09-09.dump
Флаг -F c задаёт сжатый внутренний формат, из которого удобно восстанавливать выборочно. Альтернатива — обычный SQL-файл, который можно открыть и прочитать глазами:
pg_dump -U snake_app -h localhost -d snake -f snake-2026-09-09.sql
Копия снимается с работающей базы, останавливать приложение не нужно: PostgreSQL отдаёт согласованный снимок на момент начала выгрузки.
Восстановление
Из сжатого формата восстанавливают утилитой pg_restore, из SQL-файла — обычным psql:
createdb -U postgres snake_restored
pg_restore -U postgres -d snake_restored snake-2026-09-09.dump
psql -U postgres -d snake_restored -f snake-2026-09-09.sql
Обрати внимание: восстановление идёт в новую базу snake_restored, а не поверх рабочей. Так проще проверить, что копия целая, ничего не потеряв.
Что важнее самой копии
Копия, которую никто не пробовал восстановить, — не копия, а надежда. Разворачивай последний файл в отдельную базу и смотри, на месте ли данные: обычно именно тут выясняется, что выгрузка ломается на середине или делалась не той базы.
Ещё три правила:
- Копии хранятся не там, где база. Файл на том же диске исчезнет вместе с сервером.
- Копии снимаются по расписанию, а не когда вспомнил. На сервере это задача планировщика, в облачном сервисе — встроенная функция.
- Копия делается перед каждым опасным действием: массовым удалением, миграцией, экспериментом с чужим скриптом.
Если база живёт в облачном сервисе, автоматические копии обычно входят в тариф — глубину хранения смотри в документации своего провайдера, например supabase.com/docs. Это не отменяет собственной выгрузки перед рискованными операциями.
Типичные ошибки новичка
Список ниже — то, на чём спотыкаются практически все при первом знакомстве с PostgreSQL.
Пароль пользователя postgres забыт
Классика первого дня: пароль задан в мастере установки и нигде не записан. Восстановить его нельзя, только сбросить — через файл pg_hba.conf (он отвечает за правила аутентификации): временно разрешить вход без пароля, задать новый, вернуть настройку. Полчаса возни там, где хватило бы менеджера паролей.
Порт 5432 занят
Ошибка вида «порт уже используется» при запуске означает, что на 5432 уже кто-то слушает — обычно ранее установленная копия PostgreSQL или контейнер Docker, про который ты забыл. Кто именно занял порт, покажет команда:
# Windows
netstat -ano | findstr :5432
# macOS и Linux
lsof -i :5432
Дальше два выхода: остановить лишний экземпляр либо запустить новый на другом порте, не забыв поменять порт и в строке подключения.
Приложение не может подключиться: connection refused
Сообщение connection refused значит, что до сервера не достучались вовсе. Проверять по порядку: запущена ли служба базы, тот ли порт указан, тот ли адрес. Отдельная ловушка на Windows — брандмауэр, который блокирует соединение, если база и приложение на разных машинах.
Другая формулировка, password authentication failed, говорит об обратном: сервер найден, но не пустил. Ищи опечатку в пароле или имени пользователя.
Проблемы с кодировкой и русскими буквами
Современные версии PostgreSQL создают базы в UTF-8, и с русским текстом проблем обычно нет. Но кракозябры встречаются в двух местах. Первое — консоль Windows, которая по умолчанию работает не в UTF-8; перед запуском psql кодировку переключают командой:
chcp 65001
Второе — импорт файла в другой кодировке: база получает байты в Windows-1251, а трактует их как UTF-8. Лечение — пересохранить файл в UTF-8 перед загрузкой. Проверить кодировку базы можно так:
SELECT datname, pg_encoding_to_char(encoding) FROM pg_database;
База открыта наружу без ограничений
Соблазнительный шаг при первой попытке подключиться с другой машины — разрешить подключения со всех адресов и открыть порт 5432 в интернет. Через несколько часов такую базу находят автоматические сканеры, и дальше всё зависит от стойкости пароля. Правильно — открывать доступ только тем адресам, кому он нужен: приложение с того же сервера ходит по localhost, удалённое подключение идёт через защищённый канал. Списки разрешённых адресов задаются в pg_hba.conf.
Пароль от базы в коде и в репозитории
Утёкший пароль от базы — это доступ ко всем данным пользователей, поэтому строка подключения не должна попадать в git. И если она всё-таки уехала в репозиторий, удалить её следующим коммитом мало: она останется в истории. Надёжное лечение одно — сменить пароль в базе.
Данные без ограничений на уровне базы
Соблазн проверять всё в коде приложения и оставить в базе одни text-колонки заканчивается мусором: пустые ники, отрицательные очки, результаты несуществующих игроков. База — последний рубеж, и ограничения NOT NULL, UNIQUE, CHECK и внешние ключи стоят одной строки при создании таблицы.
Запросы без индексов на растущей таблице
Пока в таблице сто строк, любой запрос быстрый. На сотне тысяч разница между запросом с индексом и без него — миллисекунды против секунд. Понять, что происходит, помогает EXPLAIN ANALYZE: он показывает, как база выполняла запрос и сколько времени на что потратила.
EXPLAIN ANALYZE
SELECT * FROM scores WHERE player_id = 1 ORDER BY score DESC LIMIT 10;
Если в выводе встречается Seq Scan по большой таблице — база перебирает строки подряд, и стоит подумать об индексе.
Отсутствие резервных копий
Самая дорогая ошибка из списка, потому что обнаруживается в момент, когда уже поздно. Настрой копии в первый же день, когда в базе появились чужие данные, а не после первого инцидента.
Частые вопросы
Нужно ли ставить PostgreSQL локально, если я работаю через Supabase?
Не обязательно. Supabase даёт готовую базу PostgreSQL в облаке вместе с веб-редактором и SQL-консолью, и для учебного проекта этого достаточно. Локальная установка полезна, когда хочешь разбираться в устройстве базы, работать без интернета или пробовать рискованные запросы, не трогая рабочие данные.
Чем PostgreSQL отличается от MySQL?
Обе базы решают одну задачу и обе бесплатны. PostgreSQL строже относится к типам данных и целостности, богаче на возможности вроде jsonb и расширений, MySQL исторически чаще встречается на массовых хостингах. Для нового проекта PostgreSQL — выбор по умолчанию, на нём же работают популярные облачные платформы.
Сколько данных выдержит PostgreSQL?
Практический потолок задают не сама база, а железо и качество запросов. Таблицы в десятки миллионов строк — обычная рабочая ситуация при нормальных индексах. Для учебного проекта и первых сотен пользователей вопрос производительности не возникает вовсе.
Что такое ORM и нужен ли он новичку?
ORM — библиотека, которая позволяет работать с таблицами как с объектами языка программирования, не написав ни строчки SQL. Удобно для типовых операций, но прячет от тебя происходящее: почему запрос медленный, ORM не объяснит. Разумный порядок — сначала разобраться в базовом SQL, а потом уже брать ORM, понимая, какой запрос он породит.
Как посмотреть данные PostgreSQL без командной строки?
Графическими клиентами: pgAdmin ставится вместе с сервером в установщике для Windows, DBeaver работает со всеми популярными базами, у облачных сервисов есть веб-редактор таблиц. Для правки отдельных строк это удобнее консоли, но psql стоит освоить хотя бы на уровне подключения — он есть везде.
Что делать, если запрос выполняется слишком долго?
Начни с EXPLAIN ANALYZE — он покажет, как база выполняет запрос. Чаще всего причина в отсутствии индекса на колонке из WHERE или ORDER BY, реже — в выборке лишних колонок и строк, которые приложению не нужны. Добавь индекс, ограничь выборку LIMIT и замерь снова.
Заключение
PostgreSQL перестаёт казаться сложным, как только разложишь его на части: сервер, который слушает порт, база внутри него, таблицы с типами колонок, связи через ключи и четыре команды SQL. Остальное — надстройки, которые осваиваются по мере надобности.
Для «Змейки» маршрут такой: общая таблица рекордов в облаке, параллельно локальная установка для понимания устройства, и с первого дня, когда в базе появились чужие данные, — резервные копии и пароль в переменных окружения, а не в коде.
Чек-лист «база готова к работе»
- PostgreSQL установлен, служба запущена,
psqlподключается. - Пароль пользователя
postgresсохранён в менеджере паролей. - Под проект заведены отдельная база и отдельный пользователь.
- Таблицы созданы с первичными ключами,
NOT NULLи внешними ключами. - На колонки из частых
WHEREиORDER BYдобавлены индексы. - Строка подключения лежит в переменной окружения,
.env— в.gitignore. - В коде используется пул соединений и параметры
$1,$2вместо склейки строк. - Изменения структуры оформляются миграциями и лежат в репозитории.
- Настроены резервные копии по расписанию, и хотя бы одна из них восстановлена для проверки.
- Порт 5432 не открыт в интернет без ограничений.
Источники
- PostgreSQL — официальная документация: postgresql.org/docs
- PostgreSQL — страница загрузки дистрибутивов: postgresql.org/download
- Supabase — документация по работе с базой данных: supabase.com/docs
Читай дальше
Все статьиНе просто статьи — тебя доведут до результата
В практикуме за 2499 ₽ рядом живая команда практикующих разработчиков и маркетологов: ведём по шагам до твоего работающего приложения. Не «ролики и сам разбирайся» — помогаем на каждом затыке.
Перейти к практикуму