PostgreSQL: как устроена реляционная база данных
Приложению нужно хранить данные дольше, чем работает один процесс Python.
Переменная:
orders = []
существует только в памяти программы. После перезапуска список исчезнет. Если приложение запущено в нескольких процессах, у каждого будет собственная копия.
Файл сохраняет данные, но быстро появляются дополнительные требования:
- найти все заказы конкретного пользователя;
- одновременно изменить несколько связанных записей;
- не допустить повторяющийся email;
- безопасно работать из нескольких процессов;
- восстановиться после сбоя;
- ограничить права доступа;
- сделать резервную копию.
Эти задачи решает система управления базами данных.
PostgreSQL - объектно-реляционная система управления базами данных. Она хранит данные, выполняет SQL-запросы, контролирует целостность и координирует параллельную работу клиентов.
Приложение
↓
SQL-запрос
↓
PostgreSQL
↓
данные и результат
PostgreSQL не является просто файлом с таблицами. Это отдельный серверный процесс со своей памятью, журналом изменений, системой транзакций, планировщиком запросов и механизмами доступа.
База данных, СУБД и SQL
Эти понятия связаны, но не совпадают.
База данных
База данных - организованный набор данных.
Например:
пользователи
заказы
товары
платежи
СУБД
СУБД управляет данными:
- сохраняет;
- читает;
- изменяет;
- проверяет ограничения;
- обслуживает одновременные запросы;
- ведёт журнал;
- управляет правами.
PostgreSQL является СУБД.
SQL
SQL - язык запросов к реляционным базам данных.
SELECT id, total
FROM orders
WHERE user_id = 42
ORDER BY created_at DESC;
Разработчик указывает, какие строки нужны. PostgreSQL сам выбирает план выполнения.
Клиент-серверная архитектура
PostgreSQL работает по клиент-серверной модели.
Сервер:
- слушает подключения;
- проверяет пользователя;
- выполняет запросы;
- читает и изменяет файлы данных;
- управляет транзакциями;
- координирует параллельную работу.
Клиентом может быть:
psql;- FastAPI-приложение;
- скрипт Python;
- панель администрирования;
- аналитический инструмент.
Строка подключения обычно выглядит так:
postgresql://user:password@host:5432/database
user - роль PostgreSQL
host - адрес сервера
5432 - стандартный порт
database - база, к которой подключается клиент
Один работающий экземпляр PostgreSQL может управлять несколькими базами данных.
Сущности, атрибуты , кортежиб домены и отношения
Перед проектированием таблиц нужно определить, какие объекты предметной области должна хранить база данных.
Сущности - Отношения
Такой объект называют сущностью. Сущностью может быть пользователь, заказ, товар, платёж или учебный курс.
У сущности есть атрибуты - свойства, которыми она описывается.
Сущность «пользователь» - id, имя, email, телефон
Сущность «заказ» - id, дата создания, сумма, статус
Сущность «товар» - id, название, цена
В реляционной модели каждая сущность представляется отношением. На практике отношение реализуется в виде таблицы.
Например, сущность «пользователь» представляется отношением users, а каждый конкретный пользователь - отдельной строкой этой таблицы.
users - отношение, представляющее пользователей
id, name, email - атрибуты отношения
одна строка users - один экземпляр сущности «пользователь»
Одна таблица должна описывать одну сущность или одну самостоятельную связь между сущностями. Если в одной таблице смешиваются пользователи, заказы и товары, одни и те же факты начинают повторяться, а операции добавления, обновления и удаления могут приводить к аномалиям.
Связи между сущностями также отражаются в реляционной модели. Для этого используются ключи, внешние ключи и отдельные таблицы связей.
Атрибуты - Столбцы
Атрибут описывает свойство объекта:
id
name
email
created_at
У каждого атрибута есть домен и ограничения.
Кортежи - Строки
Кортеж представляет экземпляр:
42 | Сергей | user@example.com | 2026-08-01 12:00:00+00
Домены - Типы данных
Тип ограничивает допустимые значения и определяет доступные операции.
Распространённые типы:
integer, bigint - целые числа
numeric - точные десятичные значения
text, varchar - строки
boolean - true / false
date, timestamp, timestamptz - дата и время
uuid - идентификатор
jsonb - структурированный JSON
array - массив значений
Для денег часто используют numeric, если нужна точная десятичная арифметика.
Для событий обычно удобен timestamptz, поскольку он представляет конкретный момент времени.
Правильный тип помогает базе:
- проверять значения;
- хранить их эффективно;
- выбирать операторы;
- строить индексы;
- предотвращать ошибки.
Ограничения
Ограничения переносят часть правил целостности на уровень базы.
NOT NULL
email text NOT NULL
UNIQUE
email text UNIQUE
CHECK
price numeric CHECK (price > 0)
FOREIGN KEY
Проверяет существование связанной строки.
Почему недостаточно проверки в Python:
два процесса одновременно проверили email
-
оба решили, что значение свободно
-
оба попытались вставить строку
Уникальное ограничение разрешает такую гонку внутри базы: одна операция проходит, другая получает конфликт.
Проверки приложения нужны для удобного интерфейса, а ограничения базы защищают сами данные.
Ключи таблицы
Ключ - атрибут или набор атрибутов, по которым можно однозначно определить строку таблицы.
Например, у пользователя могут быть следующие атрибуты:
id
email
phone
passport_number
Некоторые из них потенциально способны однозначно идентифицировать пользователя. Но не каждый уникальный на текущем наборе данных столбец автоматически является хорошим ключом.
Например, имена пользователей могут случайно не повторяться:
Сергей
Анна
Иван
Но это не делает name ключом, поскольку два пользователя могут иметь одинаковое имя.
Ключ выбирают исходя не только из текущих данных, но и из правил предметной области.
Суперключ
Суперключ - любой набор атрибутов, значения которого однозначно определяют строку.
Например:
id
id + email
id + email + name
Если id уже уникален, то любой набор, содержащий id, тоже будет уникальным.
Но дополнительные атрибуты в таком наборе избыточны.
Потенциальный ключ
Потенциальный ключ, или candidate key, - минимальный суперключ.
Минимальный означает, что из него нельзя удалить атрибут, сохранив однозначную идентификацию строки.
Для таблицы пользователей потенциальными ключами могут быть:
id
email
passport_series + passport_number
Здесь последний ключ является составным: строка определяется сочетанием двух столбцов.
Потенциальных ключей может быть несколько.
Например:
id - внутренний идентификатор
email - уникальный адрес пользователя
Оба значения могут однозначно определять строку, если это гарантируется правилами системы.
Первичный ключ
Первичный ключ - один из потенциальных ключей, выбранный как основной идентификатор строки.
CREATE TABLE users (
id bigint GENERATED ALWAYS AS IDENTITY,
email text NOT NULL,
PRIMARY KEY (id),
UNIQUE (email)
);
В этом примере:
id - первичный ключ
email - альтернативный потенциальный ключ
Ограничение PRIMARY KEY закрепляет выбор на уровне базы данных.
PostgreSQL требует, чтобы значения первичного ключа:
- не были
NULL; - не повторялись.
Для проверки этого PostgreSQL автоматически создаёт уникальный индекс. ([PostgreSQL][1])
Но наличие ограничения ещё не означает, что ключ хорошо выбран с точки зрения предметной области.
Например, технически можно написать:
PRIMARY KEY (name)
Однако это будет плохой моделью, если имена могут повторяться или изменяться.
То есть нужно разделять два вопроса:
Логический вопрос - действительно ли атрибут идентифицирует объект
Технический вопрос - обеспечивает ли база уникальность и отсутствие NULL
Альтернативный ключ
Потенциальные ключи, которые не были выбраны первичными, называют альтернативными.
Обычно они закрепляются ограничением UNIQUE.
CREATE TABLE users (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
email text NOT NULL UNIQUE
);
Здесь email остаётся потенциальным ключом, хотя первичным выбран id.
Составной первичный ключ
Первичный ключ может состоять из нескольких столбцов.
Например, студент может записываться на один курс только один раз:
CREATE TABLE enrollments (
student_id bigint NOT NULL,
course_id bigint NOT NULL,
enrolled_at timestamptz NOT NULL DEFAULT now(),
PRIMARY KEY (student_id, course_id)
);
Здесь ни student_id, ни course_id по отдельности не идентифицируют строку:
student_id - один студент может проходить несколько курсов
course_id - на одном курсе может быть несколько студентов
student_id + course_id - однозначно определяют запись
Такой ключ называется составным.
Составной ключ имеет смысл, когда уникальность определяется именно комбинацией атрибутов.
Внешний ключ
Внешний ключ - столбец или набор столбцов в одной таблице, значения которых должны соответствовать ключу в другой или той же таблице.
Пусть есть таблицы пользователей и заказов:
CREATE TABLE users (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
name text NOT NULL
);
CREATE TABLE orders (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
user_id bigint NOT NULL,
total numeric(12, 2) NOT NULL,
FOREIGN KEY (user_id)
REFERENCES users(id)
);
Здесь:
users.id - referenced key, ключ, на который ссылаются
orders.user_id - foreign key, внешний ключ
Важно:
user_idявляется внешним ключом не сам по себе, а потому что для него объявлено ограничениеFOREIGN KEY.
Без ограничения база технически позволила бы записать:
user_id = 999999
даже если такого пользователя не существует.
Аномалии данных
Если структура таблиц спроектирована неудачно, обычные операции с данными могут приводить к противоречиям. Такие проблемы называют аномалиями.
Основные виды:
Аномалия вставки - данные нельзя добавить без другой, ещё не существующей информации
Аномалия обновления - одно логическое значение приходится изменять в нескольких строках
Аномалия удаления - удаление одной строки приводит к потере другой полезной информации
Аномалия вставки
Аномалия вставки возникает, когда новый факт нельзя сохранить независимо.
Например, если данные о пользователе и его заказе находятся в одной таблице, добавить пользователя без заказа может оказаться невозможно.
Проблема появляется потому, что одна строка одновременно описывает несколько разных сущностей.
Аномалия обновления
Аномалия обновления возникает, когда одно значение хранится в нескольких местах.
Например, имя пользователя повторяется во всех его заказах. После изменения имени нужно обновить каждую связанную строку.
Если обновилась только часть строк, база начинает содержать противоречивые данные.
Аномалия удаления
Аномалия удаления возникает, когда удаление одного факта случайно удаляет другой.
Например, если информация о пользователе хранится только вместе с его заказами, удаление последнего заказа может привести к потере данных самого пользователя.
Эти аномалии обычно уменьшают разделением сущностей по отдельным таблицам и установлением связей между ними.
users - данные пользователей
orders - данные заказов
user_id - связь заказа с пользователем
Так пользователь хранится один раз, а заказы только ссылаются на него.
Нормализация базы данных
Нормализация - это способ организовать данные в реляционной базе так, чтобы уменьшить дублирование, избежать противоречий и упростить обновление информации.
Проблема обычно появляется, когда в одной таблице пытаются хранить сразу несколько сущностей.
Например:
| order_id | client_name | client_phone | product_1 | product_2 | product_3 |
|---|---|---|---|---|---|
| 101 | Анна | +79990000001 | Ноутбук | Мышь | NULL |
| 102 | Анна | +79990000001 | Клавиатура | NULL | NULL |
В такой структуре имя и телефон клиента повторяются, количество товаров ограничено числом подготовленных столбцов, а изменение телефона потребует обновления сразу нескольких строк.
Нормализация помогает разделить данные на связанные таблицы:
clients- клиенты;orders- заказы;products- товары;order_items- товары внутри заказов.
При этом таблицы связываются с помощью идентификаторов и внешних ключей.
Первая нормальная форма - 1НФ
Таблица находится в первой нормальной форме, если:
- каждая ячейка содержит одно значение;
- в таблице нет повторяющихся групп столбцов;
- каждую строку можно однозначно определить.
Структура с полями product_1, product_2 и product_3 нарушает 1НФ. Количество товаров в заказе заранее неизвестно, поэтому добавлять отдельный столбец для каждого товара нельзя.
Неправильный вариант:
| order_id | products |
|---|---|
| 101 | Ноутбук, Мышь, Клавиатура |
Поле products содержит сразу несколько значений. База не сможет нормально искать, сортировать и связывать отдельные товары.
После приведения к 1НФ каждый товар занимает отдельную строку:
| order_id | product_id | quantity |
|---|---|---|
| 101 | 15 | 1 |
| 101 | 27 | 1 |
| 101 | 31 | 2 |
Теперь можно получить все товары заказа, посчитать их количество или найти заказы с конкретным товаром обычным SQL-запросом.
Важно понимать, что значение должно быть атомарным относительно задачи. Например, полный адрес можно хранить одной строкой, если приложение всегда отображает его целиком. Если нужно отдельно фильтровать данные по городу, улице или индексу, адрес стоит разделить на соответствующие поля.
Вторая нормальная форма - 2НФ
Таблица находится во второй нормальной форме, если:
- она уже соответствует 1НФ;
- каждый неключевой столбец зависит от всего составного первичного ключа, а не только от его части.
Это правило особенно важно для таблиц с составным ключом.
Рассмотрим таблицу товаров в заказах:
| order_id | product_id | order_date | product_name | quantity |
|---|---|---|---|---|
| 101 | 15 | 2026-08-02 | Ноутбук | 1 |
| 101 | 27 | 2026-08-02 | Мышь | 2 |
Предположим, что строка определяется парой:
order_id + product_id
Однако отдельные поля зависят только от части этого ключа:
order_dateзависит только отorder_id;product_nameзависит только отproduct_id;quantityзависит от сочетанияorder_idиproduct_id.
Получается, что в одной таблице смешаны данные заказа, товара и позиции заказа.
Для приведения к 2НФ таблицу следует разделить.
Таблица заказов:
| order_id | order_date |
|---|---|
| 101 | 2026-08-02 |
Таблица товаров:
| product_id | product_name |
|---|---|
| 15 | Ноутбук |
| 27 | Мышь |
Таблица позиций заказа:
| order_id | product_id | quantity |
|---|---|---|
| 101 | 15 | 1 |
| 101 | 27 | 2 |
Теперь каждое поле хранится рядом с той сущностью, от которой оно действительно зависит.
Если таблица использует простой первичный ключ из одного столбца, частичной зависимости от ключа возникнуть не может. Поэтому нарушение 2НФ обычно рассматривают именно в таблицах с составными ключами.
Третья нормальная форма - 3НФ
Таблица находится в третьей нормальной форме, если:
- она соответствует 2НФ;
- неключевые поля не зависят друг от друга.
Иначе говоря, каждый неключевой столбец должен описывать непосредственно сущность, определяемую первичным ключом.
Рассмотрим таблицу клиентов:
| client_id | client_name | city_id | city_name |
|---|---|---|---|
| 1 | Анна | 77 | Москва |
| 2 | Иван | 78 | Санкт-Петербург |
| 3 | Мария | 77 | Москва |
Первичный ключ здесь - client_id.
Поле city_id зависит от клиента, но city_name зависит уже не от клиента, а от city_id:
client_id → city_id → city_name
Такая зависимость называется транзитивной.
Из-за нее название города повторяется в каждой строке. Если потребуется переименовать город или исправить ошибку, придется обновлять множество записей.
Для приведения таблицы к 3НФ данные о городах выносятся отдельно.
Таблица клиентов:
| client_id | client_name | city_id |
|---|---|---|
| 1 | Анна | 77 |
| 2 | Иван | 78 |
| 3 | Мария | 77 |
Таблица городов:
| city_id | city_name |
|---|---|
| 77 | Москва |
| 78 | Санкт-Петербург |
Теперь название города хранится в одном месте, а таблица клиентов содержит только ссылку на соответствующую запись.
Какие проблемы решает нормализация
Правильно нормализованная структура защищает базу от нескольких типов аномалий.
Аномалия обновления
Когда одно значение хранится в нескольких строках, его приходится изменять сразу во всех местах.
Например, если телефон клиента записан в каждом заказе, часть строк может остаться со старым номером.
Аномалия добавления
Иногда невозможно добавить одну сущность без другой.
Например, в общей таблице заказов и товаров нельзя сохранить новый товар, пока его никто не заказал.
Аномалия удаления
Удаление одной записи может случайно уничтожить другую информацию.
Например, если данные о клиенте существуют только внутри заказов, удаление последнего заказа приведет к потере информации о самом клиенте.
После нормализации клиенты, заказы и товары существуют независимо друг от друга.
Нормализация не означает максимальное количество таблиц
Цель нормализации заключается не в том, чтобы разбить базу на как можно больше частей. Каждая таблица должна представлять понятную сущность или связь между сущностями.
Для большинства прикладных систем достаточно привести структуру к третьей нормальной форме:
- 1НФ устраняет списки и повторяющиеся группы внутри строки;
- 2НФ устраняет зависимости от части составного ключа;
- 3НФ устраняет зависимости неключевых полей друг от друга.
Более высокие формы тоже существуют:
- нормальная форма Бойса - Кодда, или BCNF, усиливает требования 3НФ для сложных функциональных зависимостей;
- 4НФ устраняет многозначные зависимости;
- 5НФ работает со сложными зависимостями соединения таблиц;
- 6НФ применяется редко, преимущественно в специализированных и темпоральных базах данных.
В обычных CRM, интернет-магазинах, внутренних сервисах и системах автоматизации чаще всего достаточно корректной 3НФ. Более высокие формы становятся важны, когда структура данных содержит сложные отношения, которые нельзя безопасно выразить обычным разделением сущностей.
Когда допустима денормализация
Иногда разработчики намеренно добавляют дублирующиеся данные. Такой подход называется денормализацией.
Например, итоговую стоимость заказа можно каждый раз вычислять по его позициям:
SELECT SUM(quantity * price)
FROM order_items
WHERE order_id = 101;
Но в системе с большим количеством запросов готовое значение total_amount иногда дополнительно сохраняют в таблице orders.
Это может ускорить чтение, но создает новую обязанность: итоговая сумма должна обновляться при любом изменении состава заказа.
Поэтому сначала обычно строят нормализованную модель, а затем точечно денормализуют ее там, где это подтверждено измерениями производительности. Денормализация без конкретной причины часто возвращает те же проблемы, от которых нормализация должна была защитить.
Отношения между таблицами
Теперь типы отношений можно связать с ключами.
Один к одному
Одна строка первой таблицы соответствует не более чем одной строке второй.
CREATE TABLE user_profiles (
user_id bigint PRIMARY KEY,
FOREIGN KEY (user_id)
REFERENCES users(id)
);
user_id одновременно:
- первичный ключ
user_profiles; - внешний ключ на
users.
Уникальность не позволяет создать два профиля для одного пользователя.
Один ко многим
Один пользователь может иметь много заказов.
orders.user_id
не является уникальным:
order 1 - user_id 42
order 2 - user_id 42
order 3 - user_id 42
Каждый заказ принадлежит одному пользователю, но пользователь связан со многими заказами.
Многие ко многим
Заказ содержит много товаров, а товар может входить во многие заказы.
Используется промежуточная таблица:
CREATE TABLE order_items (
order_id bigint NOT NULL,
product_id bigint NOT NULL,
quantity integer NOT NULL,
PRIMARY KEY (order_id, product_id),
FOREIGN KEY (order_id)
REFERENCES orders(id),
FOREIGN KEY (product_id)
REFERENCES products(id)
);
Здесь составной первичный ключ:
order_id + product_id
не позволяет дважды добавить одну и ту же пару заказа и товара.
Ссылочная целостность
После разделения данных между таблицами появляется новая проблема: ссылки между ними должны оставаться корректными.
Ссылочная целостность означает, что значение внешнего ключа должно соответствовать существующей строке связанной таблицы либо быть NULL, если это разрешено моделью.
Нарушение возникает, когда дочерняя строка ссылается на несуществующую родительскую строку.
orders.user_id = 42
users.id = 42 отсутствует
Такая ссылка называется висячей. Связанную строку также могут называть orphan record.
Ограничение FOREIGN KEY не позволяет:
- создать ссылку на несуществующую строку;
- изменить внешний ключ на недопустимое значение;
- удалить или изменить родительскую строку так, чтобы нарушилась связь.
Но при удалении или изменении родительской строки база должна знать, что делать с зависимыми строками. Это задаётся ссылочными действиями.
Действия при удалении и обновлении
Правила задаются через:
ON DELETE ...
ON UPDATE ...
Основные варианты:
NO ACTION - не выполнять автоматическое действие и проверить целостность ограничения
RESTRICT - сразу запретить операцию при наличии зависимых строк
CASCADE - распространить удаление или изменение на связанные строки
SET NULL - установить внешний ключ в NULL
SET DEFAULT - установить значение внешнего ключа по умолчанию
NO ACTION
Операция не выполняет автоматических изменений зависимых строк.
Если после неё нарушается ссылочная целостность, PostgreSQL отклоняет операцию.
Это стандартное поведение по умолчанию.
RESTRICT
Запрещает удаление или изменение родительской строки, если на неё существуют ссылки.
RESTRICT близок к NO ACTION, но проверка выполняется сразу и не может быть отложена до конца транзакции.
CASCADE
Автоматически распространяет операцию на зависимые строки.
ON DELETE CASCADE - удалить связанные строки
ON UPDATE CASCADE - обновить значения внешних ключей
Каскад подходит, когда дочерняя запись не имеет самостоятельного смысла без родительской.
SET NULL
При удалении или изменении родительской строки внешний ключ получает NULL.
Это возможно только тогда, когда столбец допускает NULL.
SET DEFAULT
Внешний ключ получает значение по умолчанию.
При этом новое значение всё равно должно соответствовать правилам внешнего ключа.
Основные SQL-операции
INSERT
INSERT INTO users (name, email)
VALUES ('Сергей', 'user@example.com')
RETURNING id, name, email;
SELECT
SELECT id, name, email
FROM users
WHERE id = 42;
UPDATE
UPDATE users
SET name = 'Sergey'
WHERE id = 42
RETURNING *;
DELETE
DELETE FROM users
WHERE id = 42;
Без WHERE изменение затронет все строки.
JOIN
Данные часто разделены между таблицами.
SELECT
orders.id,
orders.total,
users.name
FROM orders
JOIN users
ON users.id = orders.user_id;
Основные варианты:
INNER JOIN - только совпавшие строки
LEFT JOIN - все строки слева и совпадения справа
Реляционная модель не требует хранить имя пользователя в каждом заказе. Заказ хранит user_id, а актуальные данные получаются соединением.
Агрегация
SELECT
user_id,
COUNT(*) AS orders_count,
SUM(total) AS total_amount,
AVG(total) AS average_order
FROM orders
GROUP BY user_id;
GROUP BY формирует группы строк.
WHERE фильтрует строки до группировки. HAVING фильтрует уже сформированные группы.
HAVING COUNT(*) >= 5
Транзакции
Транзакция объединяет несколько операций в одну логическую единицу.
Перевод между счетами:
- Списать сумму с первого счёта.
- Начислить сумму второму.
- Зафиксировать оба изменения вместе.
BEGIN;
UPDATE accounts
SET balance = balance - 1000
WHERE id = 1;
UPDATE accounts
SET balance = balance + 1000
WHERE id = 2;
COMMIT;
Если возникает ошибка:
ROLLBACK;
Свойства транзакций часто описывают как ACID:
Atomicity - все операции или ни одной
Consistency - ограничения остаются выполненными
Isolation - параллельные транзакции не создают некорректный результат
Durability - после COMMIT результат должен пережить сбой
Индексы
CREATE INDEX idx_orders_user_id
ON orders(user_id);
Индекс хранит дополнительную структуру, которая помогает быстрее находить строки.
Распространённые типы:
- B-tree;
- GIN;
- GiST;
- BRIN.
B-tree подходит для большинства сравнений и сортировок.
GIN часто применяют для массивов, полнотекстового поиска и jsonb.
Индекс имеет цену:
- занимает место;
- замедляет запись;
- требует обслуживания;
- может не использоваться планировщиком.
Поэтому индекс создают под реальные запросы, а не на каждый столбец.
Составные и частичные индексы
CREATE INDEX idx_orders_user_created
ON orders(user_id, created_at DESC);
Такой индекс подходит для:
WHERE user_id = 42
ORDER BY created_at DESC
Порядок столбцов важен.
Частичный индекс:
CREATE INDEX idx_active_orders
ON orders(created_at)
WHERE status = 'active';
Он содержит только строки, удовлетворяющие условию.
EXPLAIN
SQL описывает результат, а планировщик выбирает способ выполнения.
EXPLAIN
SELECT *
FROM orders
WHERE user_id = 42;
План может содержать:
Seq Scan
Index Scan
Nested Loop
Hash Join
Merge Join
EXPLAIN ANALYZE реально выполняет запрос и показывает фактические показатели:
EXPLAIN (ANALYZE, BUFFERS)
SELECT ...
С UPDATE и DELETE его используют осторожно, поскольку операция будет выполнена.
JSONB
PostgreSQL умеет хранить JSON.
CREATE TABLE events (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
event_type text NOT NULL,
payload jsonb NOT NULL
);
Запрос:
SELECT *
FROM events
WHERE payload->>'order_id' = 'order_1045';
jsonb полезен для:
- интеграционных payload;
- метаданных;
- данных с изменяемым набором полей.
Но критичные поля для связей, ограничений, сортировки и частого поиска обычно лучше хранить отдельными столбцами.
Роли и права
PostgreSQL использует роли.
CREATE ROLE app_user
LOGIN
PASSWORD 'secret';
Права выдаются явно:
GRANT CONNECT ON DATABASE app TO app_user;
GRANT USAGE ON SCHEMA public TO app_user;
GRANT SELECT, INSERT, UPDATE ON orders TO app_user;
Приложение не должно без необходимости подключаться как суперпользователь.
PostgreSQL и FastAPI
FastAPI не содержит встроенного драйвера базы данных.
Используют:
psycopg;asyncpg;- SQLAlchemy;
- SQLModel.
Типовая архитектура:
FastAPI endpoint
↓
service
↓
repository
↓
драйвер или ORM
↓
PostgreSQL
Приложение обычно использует пул соединений.
Схема базы должна изменяться через миграции. В Python-проектах с SQLAlchemy часто используют Alembic.
Когда PostgreSQL подходит
PostgreSQL особенно полезен, если нужны:
- связанные данные;
- транзакции;
- ограничения целостности;
- JOIN и агрегации;
- несколько одновременных клиентов;
- надёжное долговременное хранение;
- JSON рядом с реляционными полями.
Примеры:
- CRM;
- интернет-магазин;
- система заказов;
- платежи;
- backend FastAPI;
- аналитические витрины.
Типичные ошибки
- хранить всё в одной таблице;
- не создавать ограничения;
- индексировать каждый столбец;
- выполнять запросы по одному в цикле;
- держать транзакцию открытой слишком долго;
- использовать
SELECT *без необходимости; - подключаться суперпользователем;
- не проверять восстановление backup;
- менять production-схему вручную без миграций.
Итог
PostgreSQL - серверная реляционная СУБД, которая хранит данные и обеспечивает их согласованность.
database - база внутри СУБД
schema - пространство имён
table - отношение строк и столбцов
primary key - идентификатор строки
foreign key - внешняя связь между таблицами
transaction - единая группа изменений
index - структура для ускорения доступа
role - пользователь и права
После PostgreSQL логично изучить Redis, который решает задачи быстрого временного хранения, кэша, счётчиков и событий. Затем обе системы можно объединить с FastAPI через Docker Compose.