← Назад к вопросам

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

2.0 Middle🔥 121 комментариев
#Другое

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Партицирование

Партицирование (Partitioning) - это техника разделения большой таблицы или индекса базы данных на несколько более мелких частей (партиций), которые могут храниться отдельно и запрашиваться независимо. Это критичная техника для масштабирования больших баз данных и повышения производительности.

Основные концепции

Проблема: большие таблицы (миллионы/миллиарды строк) становятся медленными при поиске и индексировании.

Решение: разделить таблицу на логические части, чтобы каждая часть была меньше и быстрее.

Партиция (Partition) - это подмножество данных таблицы, физически отделённое от других партиций.

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

1. Range Partitioning (по диапазону)

Разделяет данные по диапазонам значений (часто по датам):

CREATE TABLE orders (
    id BIGINT,
    user_id BIGINT,
    amount DECIMAL(10, 2),
    created_at TIMESTAMP,
    PRIMARY KEY (id, created_at)
) PARTITION BY RANGE (YEAR(created_at)) (
    PARTITION p2020 VALUES LESS THAN (2021),
    PARTITION p2021 VALUES LESS THAN (2022),
    PARTITION p2022 VALUES LESS THAN (2023),
    PARTITION p2023 VALUES LESS THAN (2024),
    PARTITION p2024 VALUES LESS THAN (2025),
    PARTITION p_future VALUES LESS THAN MAXVALUE
);

Преимущества:

  • Легко добавлять новые партиции (напр. каждый год)
  • Удобно архивировать старые партиции
  • Естественно для временных данных

Когда использовать:

  • Таблицы с логами (по дате)
  • Исторические данные
  • Таблицы со временным жизненным циклом

2. Hash Partitioning (по хешу)

Разделяет данные равномерно по хешу значения:

CREATE TABLE users (
    id BIGINT PRIMARY KEY,
    email VARCHAR(255),
    username VARCHAR(100),
    created_at TIMESTAMP
) PARTITION BY HASH(id) PARTITIONS 8;

Данные распределяются равномерно по 8 партициям.

Преимущества:

  • Равномерное распределение
  • Простое добавление новых партиций
  • Хорошо для небольших наборов данных

Когда использовать:

  • Когда нет естественного ключа разделения
  • Для балансировки нагрузки

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

Разделяет данные по конкретному набору значений:

CREATE TABLE sales (
    id BIGINT PRIMARY KEY,
    region VARCHAR(50),
    amount DECIMAL(10, 2),
    sale_date DATE
) PARTITION BY LIST (region) (
    PARTITION p_north VALUES IN ('USA', 'Canada', 'Mexico'),
    PARTITION p_europe VALUES IN ('UK', 'Germany', 'France', 'Italy'),
    PARTITION p_asia VALUES IN ('China', 'Japan', 'India'),
    PARTITION p_other VALUES IN (DEFAULT)
);

Преимущества:

  • Логичное разделение по категориям
  • Простое управление

Когда использовать:

  • Данные разделены по регионам
  • По типам пользователей
  • По статусам

4. Composite Partitioning (комбинированное)

Комбинирует несколько методов партицирования:

CREATE TABLE events (
    event_id BIGINT,
    user_id BIGINT,
    event_type VARCHAR(50),
    created_at TIMESTAMP,
    PRIMARY KEY (event_id, created_at, user_id)
) PARTITION BY RANGE (YEAR(created_at))
SUBPARTITION BY LIST (event_type) (
    PARTITION p2023 (
        SUBPARTITION p2023_login VALUES IN ('login'),
        SUBPARTITION p2023_logout VALUES IN ('logout'),
        SUBPARTITION p2023_other VALUES IN (DEFAULT)
    ),
    PARTITION p2024 (
        SUBPARTITION p2024_login VALUES IN ('login'),
        SUBPARTITION p2024_logout VALUES IN ('logout'),
        SUBPARTITION p2024_other VALUES IN (DEFAULT)
    )
);

Пример: большая таблица логов

-- До партицирования: медленные запросы на миллиарды строк
CREATE TABLE logs (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    service_name VARCHAR(100),
    level VARCHAR(10),
    message TEXT,
    created_at TIMESTAMP,
    INDEX idx_service_date (service_name, created_at),
    INDEX idx_created_at (created_at)
);

-- Этот запрос может быть медленным
SELECT * FROM logs 
WHERE service_name = 'payment-service' 
AND created_at > DATE_SUB(NOW(), INTERVAL 1 DAY);

С партицированием (Range по месяцам):

-- Партициониируем по месяцам
CREATE TABLE logs (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    service_name VARCHAR(100),
    level VARCHAR(10),
    message TEXT,
    created_at TIMESTAMP,
    INDEX idx_service (service_name),
    INDEX idx_level (level)
) PARTITION BY RANGE (YEAR_MONTH(created_at)) (
    PARTITION p202401 VALUES LESS THAN (202402),
    PARTITION p202402 VALUES LESS THAN (202403),
    PARTITION p202403 VALUES LESS THAN (202404),
    PARTITION p202404 VALUES LESS THAN (202405),
    PARTITION p202405 VALUES LESS THAN (202406),
    PARTITION p_future VALUES LESS THAN MAXVALUE
);

-- Тот же запрос теперь пересекает только 1-2 партиции
SELECT * FROM logs 
WHERE service_name = 'payment-service' 
AND created_at > DATE_SUB(NOW(), INTERVAL 1 DAY);

Шардирование (Sharding) - распределённое партицирование

Разница между партицированием и шардированием:

Партицирование:

  • Все партиции на одном сервере БД
  • Управляется самой БД
  • Прозрачно для приложения

Шардирование (Sharding):

  • Каждый шард на отдельном сервере БД
  • Управляется на уровне приложения
  • Требует маршрутизации запросов
// Пример шардирования в приложении
public class UserShardRouter {
    private final UserDatabase[] shards; // 8 БД серверов
    
    public UserShardRouter(int shardCount) {
        this.shards = new UserDatabase[shardCount];
        for (int i = 0; i < shardCount; i++) {
            this.shards[i] = new UserDatabase("db-shard-" + i);
        }
    }
    
    // Определяем, в каком шарде находится пользователь
    private int getShardId(Long userId) {
        return (int) (userId % shards.length);
    }
    
    public User findUser(Long userId) {
        int shardId = getShardId(userId);
        return shards[shardId].findById(userId);
    }
    
    public void saveUser(User user) {
        int shardId = getShardId(user.getId());
        shards[shardId].save(user);
    }
}

Проблемы при партицировании

1. Partition Pruning не работает

// Плохо - БД не может использовать partition pruning
public List<Order> getOrdersByYear(int year) {
    // Функция в WHERE clause предотвращает pruning
    return orderRepository.findAll((root, query, cb) ->
        cb.equal(cb.function("YEAR", Integer.class, root.get("createdAt")), year)
    );
}

// Хорошо - partition pruning работает
public List<Order> getOrdersByDate(LocalDate startDate, LocalDate endDate) {
    return orderRepository.findByCreatedAtBetween(startDate, endDate);
    // БД может отсечь партиции, не попадающие в диапазон
}

2. Hot Partition

Когда одна партиция получает намного больше операций, чем другие:

-- Если партицируем по статусу, но 90% заказов в статусе "active"
CREATE TABLE orders (
    ...
) PARTITION BY LIST (status) (
    PARTITION p_active VALUES IN ('active'),    -- 90% данных
    PARTITION p_pending VALUES IN ('pending'),  -- 5% данных
    PARTITION p_completed VALUES IN ('completed') -- 5% данных
);

-- Решение: партицируем по другому ключу
CREATE TABLE orders (
    ...
) PARTITION BY HASH(id) PARTITIONS 16;

Best Practices

1. Выбери правильный ключ партицирования

-- Хорошие ключи
- created_at (для логов, временных данных)
- user_id (для распределения нагрузки)
- region (для географического разделения)
- status (для логического разделения)

-- Плохие ключи
- Редко используемые поля
- Поля с очень мало уникальных значений
- Поля, по которым часто делают JOIN'ы между партициями

2. Стратегия для разных типов данных

-- Таблица логов: Range по дате
CREATE TABLE logs (
    ...
) PARTITION BY RANGE (created_at) (
    PARTITION p_current VALUES LESS THAN (CURDATE()),
    PARTITION p_tomorrow VALUES LESS THAN (DATE_ADD(CURDATE(), INTERVAL 1 DAY))
);

-- Таблица пользователей: Hash
CREATE TABLE users (
    ...
) PARTITION BY HASH(id) PARTITIONS 16;

-- Таблица продаж: List по стране
CREATE TABLE sales (
    ...
) PARTITION BY LIST (country_code) (
    PARTITION p_usa VALUES IN ('US'),
    PARTITION p_eu VALUES IN ('DE', 'FR', 'UK'),
    PARTITION p_other VALUES IN (DEFAULT)
);

3. Мониторинг партиций

-- Посмотри информацию о партициях
SELECT 
    partition_name,
    partition_expression,
    partition_description,
    rows,
    data_length
FROM information_schema.partitions
WHERE table_schema = 'mydb' 
AND table_name = 'orders';

4. Добавление новых партиций

-- Для range партицирования
ALTER TABLE orders
ADD PARTITION (
    PARTITION p2025 VALUES LESS THAN (2026)
);

-- Для hash партицирования (перебалансирует данные!)
ALTER TABLE users
PARTITION BY HASH(id) PARTITIONS 16;  -- увеличили с 8 до 16

5. Удаление старых партиций

-- Удали старые архивные партиции
ALTER TABLE logs DROP PARTITION p201901, p201902, p201903;

Когда использовать партицирование

Используй партицирование, если:

  • Таблица больше 1 ГБ
  • Частые запросы по диапазону значений
  • Нужно архивировать старые данные
  • Нужно оптимизировать индексы
  • Нужна высокая пропускная способность на больших таблицах

Не используй партицирование, если:

  • Таблица небольшая (< 100 МБ)
  • Нет паттернов доступа к данным
  • Приложение не готово к сложности

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