Что такое partitioning в PostgreSQL?

SeniorPostgreSQL · Backend·Обновлено 17 июля 2026
Коротко
Partitioning — это разбиение одной логической таблицы на несколько физических частей (секций), каждая из которых хранит подмножество данных. Это улучшает производительность запросов, упрощает управление данными и ускоряет операции удаления устаревших записей.

Что такое партиционирование в PostgreSQL

Партиционирование (partitioning) — механизм разбиения большой таблицы на несколько меньших физических таблиц, называемых секциями (partitions). С точки зрения приложения работа идёт с единой логической таблицей, а PostgreSQL автоматически маршрутизирует запросы к нужным секциям.

Нативное декларативное партиционирование появилось в PostgreSQL 10 и значительно улучшалось вплоть до версии 14.

Типы партиционирования

Range (по диапазону)

Секции определяются непересекающимися диапазонами значений ключа. Классический пример — партиционирование по дате.

CREATE TABLE orders (
    id BIGSERIAL,
    created_at TIMESTAMPTZ NOT NULL,
    amount NUMERIC
) PARTITION BY RANGE (created_at);

-- Секция для Q1 2025
CREATE TABLE orders_2025_q1
    PARTITION OF orders
    FOR VALUES FROM ('2025-01-01') TO ('2025-04-01');

-- Секция для Q2 2025
CREATE TABLE orders_2025_q2
    PARTITION OF orders
    FOR VALUES FROM ('2025-04-01') TO ('2025-07-01');

List (по списку значений)

Секции определяются конкретными значениями ключа.

CREATE TABLE users (
    id BIGSERIAL,
    region TEXT NOT NULL,
    email TEXT
) PARTITION BY LIST (region);

CREATE TABLE users_europe
    PARTITION OF users
    FOR VALUES IN ('DE', 'FR', 'PL', 'RU');

CREATE TABLE users_america
    PARTITION OF users
    FOR VALUES IN ('US', 'CA', 'MX');

Hash (по хэшу)

Данные распределяются по секциям на основе хэш-функции от ключа. Используется для равномерного распределения без естественного критерия.

CREATE TABLE events (
    id BIGSERIAL,
    user_id BIGINT NOT NULL,
    payload JSONB
) PARTITION BY HASH (user_id);

CREATE TABLE events_p0
    PARTITION OF events
    FOR VALUES WITH (MODULUS 4, REMAINDER 0);

CREATE TABLE events_p1
    PARTITION OF events
    FOR VALUES WITH (MODULUS 4, REMAINDER 1);

Partition Pruning

Ключевое преимущество партиционирования — планировщик может исключить нерелевантные секции из плана выполнения запроса. Это называется partition pruning.

-- PostgreSQL просканирует только секцию orders_2025_q1
EXPLAIN SELECT * FROM orders
WHERE created_at BETWEEN '2025-01-01' AND '2025-03-31';

Управление жизненным циклом данных

Партиционирование радикально упрощает удаление устаревших данных: вместо медленного DELETE можно мгновенно отсоединить секцию.

-- Быстрое удаление целой секции (DROP + detach)
DROP TABLE orders_2024_q1;

-- Или отсоединить секцию без удаления данных
ALTER TABLE orders DETACH PARTITION orders_2024_q1;

Индексы и ограничения на секционированных таблицах

Индекс, созданный на родительской таблице, автоматически создаётся на всех секциях. Primary key и unique constraint обязаны включать ключ партиционирования.

-- Индекс создаётся на всех секциях автоматически
CREATE INDEX ON orders (created_at);

Когда партиционирование помогает

  • Таблица содержит сотни миллионов и более строк
  • Запросы регулярно фильтруют по ключу партиционирования
  • Необходимо регулярно удалять или архивировать старые данные
  • Нужно разместить «горячие» и «холодные» данные на разных tablespace

Ограничения

  • Уникальные индексы и PK должны включать ключ партиционирования
  • Foreign keys на партиционированную таблицу поддерживаются с PostgreSQL 12
  • Чрезмерное количество секций увеличивает время планирования запроса

Что хочет услышать интервьюер

Знание трёх типов партиционирования (range, list, hash) и понимание, когда применять каждый

Объяснение механизма partition pruning и его влияния на производительность

Понимание операций жизненного цикла данных: DETACH/ATTACH секций вместо массовых DELETE

Знание ограничений: ключ партиционирования в PK/unique, поведение индексов на дочерних таблицах

Практический опыт: при каком объёме данных партиционирование начинает давать реальный выигрыш

Пример: Range-партиционирование по дате с автоматическим созданием секций

-- Родительская таблица
CREATE TABLE logs (
    id BIGSERIAL,
    logged_at TIMESTAMPTZ NOT NULL,
    level TEXT,
    message TEXT
) PARTITION BY RANGE (logged_at);

-- Секция для текущего месяца
CREATE TABLE logs_2025_07
    PARTITION OF logs
    FOR VALUES FROM ('2025-07-01') TO ('2025-08-01');

-- Секция по умолчанию для данных вне диапазонов
CREATE TABLE logs_default
    PARTITION OF logs DEFAULT;

-- Индекс автоматически применяется ко всем секциям
CREATE INDEX ON logs (logged_at DESC);

-- Запрос использует partition pruning — сканируется только logs_2025_07
EXPLAIN (ANALYZE, BUFFERS)
SELECT * FROM logs
WHERE logged_at >= '2025-07-01' AND logged_at < '2025-08-01'
  AND level = 'ERROR';

-- Мгновенное удаление старых данных без блокировки таблицы
DROP TABLE logs_2024_07;

Пример: Hash-партиционирование для равномерного распределения нагрузки

-- Таблица событий, распределённых по user_id
CREATE TABLE user_events (
    id BIGSERIAL,
    user_id BIGINT NOT NULL,
    event_type TEXT NOT NULL,
    occurred_at TIMESTAMPTZ DEFAULT NOW(),
    PRIMARY KEY (id, user_id)  -- user_id обязан входить в PK
) PARTITION BY HASH (user_id);

-- Создаём 8 секций для равномерного распределения
DO $$
BEGIN
    FOR i IN 0..7 LOOP
        EXECUTE format(
            'CREATE TABLE user_events_p%s PARTITION OF user_events '
            'FOR VALUES WITH (MODULUS 8, REMAINDER %s)',
            i, i
        );
    END LOOP;
END;
$$;

-- Проверяем распределение данных по секциям
SELECT
    child.relname AS partition_name,
    pg_size_pretty(pg_relation_size(child.oid)) AS size
FROM pg_inherits
JOIN pg_class parent ON pg_inherits.inhparent = parent.oid
JOIN pg_class child  ON pg_inherits.inhrelid  = child.oid
WHERE parent.relname = 'user_events'
ORDER BY child.relname;

Типичные ошибки

Применение партиционирования к небольшим таблицам — накладные расходы на планирование превысят выгоду

Создание слишком большого числа секций (тысячи), что значительно замедляет планировщик

Попытка создать unique constraint без включения ключа партиционирования

Использование DELETE для очистки старых данных вместо DROP/DETACH секции

Путаница между партиционированием и шардингом: партиционирование работает в рамках одного узла БД

Лучшие курсы по теме

изображение курса

Docker и Ansible

Антон Ларичев
AI-тренажерыAI-тренажеры
Гарантия
Бонусы
иконка звёздочки рейтинга4.7
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Node.js с нуля

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.8
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Nest.js с нуля

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.6
3 999 ₽ 6 990 ₽
Подробнее