Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
# Горизонтальное партиционирование (Horizontal Partitioning) в базах данных
Определение
Горизонтальное партиционирование (Horizontal Partitioning) - это техника разделения данных таблицы на несколько физических частей (партиций) так, чтобы каждая партиция содержала различные строки (записи) на основе критерия разделения, но все партиции имеют одинаковую структуру (схему).
Это отличается от вертикального партиционирования, где разные колонки хранятся отдельно.
Основная идея
Оригинальная таблица USERS (100 млн строк):
id | name | email | country | created_date
1 | John | john@... | USA | 2020-01-01
2 | Jane | jane@... | UK | 2020-01-05
...
100000000 | Bob | bob@... | DE | 2023-12-31
↓↓↓ Горизонтальное партиционирование ↓↓↓
Partition 1: USERS_USA (данные для USA)
id | name | email | country | created_date
1 | John | john@... | USA | 2020-01-01
..
Partition 2: USERS_UK (данные для UK)
id | name | email | country | created_date
2 | Jane | jane@... | UK | 2020-01-05
..
Partition 3: USERS_DE (данные для DE)
id | name | email | country | created_date
...
Способы горизонтального партиционирования
1. Range Partitioning (разделение по диапазону)
Данные разделяются по диапазону значений колонки:
-- Разделение таблицы ORDERS по дате
CREATE TABLE orders (
order_id INT,
customer_id INT,
order_date DATE,
amount DECIMAL(10, 2)
)
PARTITION BY RANGE (YEAR(order_date)) (
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 p_future VALUES LESS THAN MAXVALUE
);
-- Запросы только для нужных партиций
SELECT * FROM orders WHERE order_date >= '2023-01-01';
-- MySQL будет искать только в p2023 и p_future
2. List Partitioning (разделение по списку)
Данные разделяются по дискретным значениям:
-- Разделение таблицы USERS по странам
CREATE TABLE users (
user_id INT,
name VARCHAR(100),
country VARCHAR(2),
email VARCHAR(100)
)
PARTITION BY LIST (country) (
PARTITION p_usa VALUES IN ('US', 'CA', 'MX'),
PARTITION p_eu VALUES IN ('GB', 'DE', 'FR', 'IT'),
PARTITION p_asia VALUES IN ('CN', 'JP', 'IN'),
PARTITION p_other VALUES IN (DEFAULT)
);
-- Запрос использует только p_eu
SELECT * FROM users WHERE country IN ('GB', 'FR');
3. Hash Partitioning (хеширование)
Данные разделяются по хеш-функции от колонки:
-- Разделение таблицы TRANSACTIONS на 4 партиции по хешу user_id
CREATE TABLE transactions (
trans_id INT,
user_id INT,
amount DECIMAL(10, 2),
trans_date DATE
)
PARTITION BY HASH (user_id)
PARTITIONS 4;
-- Эквивалентно:
-- Partition 0: user_id % 4 == 0
-- Partition 1: user_id % 4 == 1
-- Partition 2: user_id % 4 == 2
-- Partition 3: user_id % 4 == 3
4. Key Partitioning
Похож на Hash, но управляется базой данных:
CREATE TABLE products (
product_id INT PRIMARY KEY,
name VARCHAR(100),
price DECIMAL(10, 2)
)
PARTITION BY KEY (product_id)
PARTITIONS 8;
Преимущества горизонтального партиционирования
1. Улучшение производительности
// Без партиционирования:
// SELECT * FROM users WHERE created_date >= '2023-01-01'
// Сканирует ВСЕ 100 млн строк
// С партиционированием по year(created_date):
// Сканирует только партицию p2023 и p_future
// Может быть в 10x быстрее
2. Параллельная обработка
// Запросы к разным партициям могут выполняться параллельно
Selectin * FROM users WHERE country = 'US'; // p_usa
Select * FROM users WHERE country = 'FR'; // p_eu (параллельно)
3. Легче управление большими таблицами
-- Удаление старых данных за раз (вместо миллионов DELETE)
ALTER TABLE orders DROP PARTITION p2020; -- удаляет всю партицию мгновенно
-- Вместо:
DELETE FROM orders WHERE YEAR(order_date) = 2020; -- медленно
4. Лучшее масштабирование
Каждая партиция может располагаться на отдельном диске или даже сервере (при распределённых БД).
5. Архивирование данных
-- Старые партиции можно переместить на более медленное хранилище
ALTER TABLE orders REORGANIZE PARTITION p2020
INTO (PARTITION p2020 DATA DIRECTORY='/archive/');
Недостатки
1. Сложность запросов
// Без партиционирования:
SELECT * FROM orders; // Простой запрос
// С партиционированием:
SELECT * FROM orders; // БД сама обойдёт все партиции
// Медленнее, чем запрос конкретной партиции
2. Uneven distribution
-- Если неправильно выбрать ключ партиционирования,
-- данные распределятся неравномерно:
PARTITION BY LIST (country) (
PARTITION p_usa VALUES IN ('US'), -- 50 млн строк
PARTITION p_other VALUES IN (DEFAULT) -- 50 млн строк остальные
);
3. JOIN сложнее
-- Если JOIN'ишь таблицы с разными ключами партиционирования,
-- может потребоваться обход всех партиций обеих таблиц
SELECT * FROM orders o
JOIN customers c ON o.customer_id = c.id
WHERE o.order_date >= '2023-01-01';
Пример в Java/JPA
К сожалению, JPA не поддерживает партиционирование напрямую. Это управляется на уровне БД.
// Entity описание
@Entity
@Table(name = "orders")
public class Order {
@Id
@GeneratedValue
private Long orderId;
@Column
private Long customerId;
@Column
private LocalDate orderDate; // Ключ партиционирования
@Column
private BigDecimal amount;
}
// Сама таблица партиционируется SQL запросом:
// CREATE TABLE orders (...) PARTITION BY RANGE (YEAR(order_date)) (...)
@Repository
public interface OrderRepository extends JpaRepository<Order, Long> {
// Запрос использует правильную партицию благодаря WHERE условию
List<Order> findByOrderDateBetween(LocalDate start, LocalDate end);
}
// Использование
public void getRecentOrders() {
LocalDate startDate = LocalDate.of(2023, 1, 1);
LocalDate endDate = LocalDate.now();
// БД сама выберет нужные партиции
List<Order> orders = orderRepository.findByOrderDateBetween(startDate, endDate);
}
Сравнение с Sharding
Партиционирование (Partitioning)
- На одном сервере
- Одна таблица разделена логически
- Прозрачно для приложения
- БД управляет автоматически
Sharding (Шардирование)
- На нескольких серверах
- Разные части на разных БД
- Требует логики в приложении
- Приложение выбирает, в какой шард писать/читать
// Sharding требует логики в приложении
@Service
public class OrderService {
@Autowired
private List<OrderRepository> shards; // N БД
public void saveOrder(Order order) {
// Выбираем шард на основе user_id
int shardId = order.getCustomerId() % shards.size();
shards.get(shardId).save(order);
}
public List<Order> getOrders(Long customerId) {
int shardId = customerId % shards.size();
return shards.get(shardId).findByCustomerId(customerId);
}
}
Практические рекомендации
Когда использовать партиционирование:
- Очень большие таблицы (> 100M строк)
- Частые запросы к подмножеству данных (по дате, стране, ID)
- Архивирование/удаление старых данных регулярно
- Однозначный ключ партиционирования
- Вся таблица на одном сервере
Когда использовать sharding:
- Данные не помещаются на одном сервере
- Нужна репликация на несколько ДЦ
- Горизонтальное масштабирование критично
- Данные естественно разделяются по tenant_id, customer_id и т.д.
Ключевые моменты
- Горизонтальное партиционирование разделяет СТРОКИ (не колонки)
- Все партиции имеют одинаковую схему
- БД прозрачно переводит запросы на нужные партиции (partition pruning)
- Range и List - лучше для аналитики и архивирования
- Hash - лучше для равномерного распределения
- Требует выбора правильного ключа разделения