Секционирование MySQL для больших наборов данных

MySQLBeginner
Практиковаться сейчас

Введение

В этой лабораторной работе вы научитесь реализовывать секционирование MySQL для больших наборов данных с целью повышения производительности запросов и упрощения управления данными. Лабораторная работа фокусируется на секционировании таблицы по диапазону, в частности, с использованием столбца sale_date таблицы sales.

Вы начнете с подключения к серверу MySQL и создания базы данных sales_data. Затем вы создадите таблицу sales, секционировав ее по году sale_date на секции для 2020, 2021, 2022, 2023 годов и будущую секцию. Последующие шаги будут охватывать запросы данных из конкретных секций, реорганизацию секций с помощью ALTER TABLE и проверку влияния секционирования на скорость запросов.

Примечание: Для этой лабораторной работы вам нужно будет войти в оболочку MySQL только один раз в начале и выйти в конце. Все SQL-команды в следующих шагах должны выполняться в рамках одной и той же сессии MySQL.

Создание секционированной таблицы

На этом шаге мы создадим базу данных и секционированную таблицу в MySQL. Секционирование помогает управлять большими наборами данных, разделяя таблицу на более мелкие, управляемые части на основе заданного правила. Это может значительно повысить производительность запросов, особенно для запросов, которые фильтруют данные на основе ключа секционирования.

Сначала откройте терминал в виртуальной машине LabEx. Вы должны уже находиться в каталоге ~/project.

Подключитесь к серверу MySQL от имени пользователя root (сделайте это только один раз в начале лабораторной работы):

sudo mysql -u root

Теперь вы находитесь в оболочке MySQL. Все последующие SQL-команды должны выполняться в этой сессии до завершения лабораторной работы.

Создадим базу данных с именем sales_data для хранения нашей таблицы:

CREATE DATABASE sales_data;

Переключитесь на только что созданную базу данных:

USE sales_data;

Теперь мы создадим таблицу с именем sales и секционируем ее по году столбца sale_date. Мы создадим секции для 2020, 2021, 2022, 2023 годов и секцию для будущих дат.

CREATE TABLE sales (
    sale_id INT NOT NULL,
    sale_date DATE NOT NULL,
    amount DECIMAL(10, 2) NOT NULL,
    PRIMARY KEY (sale_id, sale_date)
)
PARTITION BY RANGE (YEAR(sale_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 pFuture VALUES LESS THAN MAXVALUE
);

Давайте разберем предложение PARTITION BY RANGE:

  • PARTITION BY RANGE (YEAR(sale_date)): Это указывает, что таблица будет секционирована на основе диапазона значений, возвращаемых функцией YEAR(), примененной к столбцу sale_date.
  • PARTITION p2020 VALUES LESS THAN (2021): Это создает секцию с именем p2020. Любая строка, в которой год sale_date меньше 2021 (т.е. 2020), будет храниться в этой секции.
  • PARTITION p2021 VALUES LESS THAN (2022): Это создает секцию с именем p2021 для данных за 2021 год.
  • PARTITION p2022 VALUES LESS THAN (2023): Это создает секцию с именем p2022 для данных за 2022 год.
  • PARTITION p2023 VALUES LESS THAN (2024): Это создает секцию с именем p2023 для данных за 2023 год.
  • PARTITION pFuture VALUES LESS THAN MAXVALUE: Это создает секцию с именем pFuture, которая будет хранить любые данные с годом sale_date больше или равным 2024. MAXVALUE — это специальное значение, которое всегда больше любого другого значения.

После выполнения оператора CREATE TABLE вы можете проверить структуру таблицы и ее секции с помощью следующей команды:

SHOW CREATE TABLE sales;

Найдите в выводе предложение PARTITION BY RANGE, чтобы убедиться, что таблица была создана с указанными секциями.

Теперь давайте вставим некоторые примеры данных в таблицу sales. MySQL автоматически поместит каждую строку в соответствующую секцию на основе sale_date.

INSERT INTO sales (sale_id, sale_date, amount) VALUES
(1, '2020-12-31', 100.00),
(2, '2021-01-15', 150.00),
(3, '2021-12-25', 200.00),
(4, '2022-06-01', 120.00),
(5, '2022-12-31', 180.00),
(6, '2023-03-10', 250.00),
(7, '2023-09-20', 300.00),
(8, '2024-01-01', 350.00);

Вы успешно создали секционированную таблицу и вставили в нее данные. На следующем шаге мы научимся запрашивать данные из конкретных секций.

Запрос данных из конкретных секций

На этом шаге мы рассмотрим, как эффективно запрашивать данные из секционированной таблицы, нацеливаясь на конкретные секции. Это одно из основных преимуществ секционирования, поскольку оно позволяет MySQL сканировать только релевантные секции, значительно уменьшая объем обрабатываемых данных и повышая производительность запросов.

Напоминание: Вы все еще должны находиться в оболочке MySQL и использовать базу данных sales_data. Если это не так, используйте:

USE sales_data;

Чтобы запрашивать данные из конкретной секции, вы можете включить условие WHERE, которое фильтрует по ключу секционирования. Оптимизатор запросов MySQL часто достаточно умен, чтобы определить, какие секции релевантны, на основе условия WHERE.

Например, чтобы получить все продажи за 2021 год, вы можете использовать следующий запрос. Обратите внимание, что мы используем прямое условие диапазона для sale_date. Использование функций, таких как YEAR(sale_date), в предложении WHERE может помешать MySQL использовать отсечение секций (partition pruning), что приведет к сканированию всех секций.

SELECT * FROM sales WHERE sale_date >= '2021-01-01' AND sale_date < '2022-01-01';

Чтобы увидеть, какие секции MySQL получает для этого запроса, вы можете использовать оператор EXPLAIN PARTITIONS:

EXPLAIN PARTITIONS SELECT * FROM sales WHERE sale_date >= '2021-01-01' AND sale_date < '2022-01-01';

В выводе EXPLAIN PARTITIONS обратите внимание на столбец partitions. Он должен показывать p2021, что указывает на то, что MySQL сканирует только секцию p2021 для выполнения этого запроса.

+----+-------------+-------+------------+------+---------------+------+---------+------+------+-------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | Extra       |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+-------------+
|  1 | SIMPLE      | sales | p2021      | ALL  | PRIMARY       | NULL | NULL    | NULL |    2 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+-------------+

Вы также можете запрашивать данные, охватывающие несколько секций. Например, чтобы получить данные о продажах за 2022 и 2023 годы:

SELECT * FROM sales WHERE sale_date >= '2022-01-01' AND sale_date < '2024-01-01';

Повторное использование EXPLAIN PARTITIONS покажет, что MySQL обращается к секциям p2022 и p2023:

EXPLAIN PARTITIONS SELECT * FROM sales WHERE sale_date >= '2022-01-01' AND sale_date < '2024-01-01';

В столбце partitions будет показано p2022,p2023.

+----+-------------+-------+---------------+------+---------------+------+---------+------+------+-------------+
| id | select_type | table | partitions    | type | possible_keys | key  | key_len | ref  | rows | Extra       |
+----+-------------+-------+---------------+------+---------------+------+---------+------+------+-------------+
|  1 | SIMPLE      | sales | p2022,p2023   | ALL  | PRIMARY       | NULL | NULL    | NULL |    4 | Using where |
+----+-------------+-------+---------------+------+---------------+------+---------+------+------+-------------+

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

Чтобы увидеть количество строк в каждой секции, вы можете запросить таблицу INFORMATION_SCHEMA.PARTITIONS:

SELECT
    PARTITION_NAME,
    TABLE_ROWS
FROM
    INFORMATION_SCHEMA.PARTITIONS
WHERE
    TABLE_SCHEMA = 'sales_data' AND TABLE_NAME = 'sales';

Этот запрос предоставляет четкое представление о том, как данные распределены по вашим секциям.

+----------------+------------+
| PARTITION_NAME | TABLE_ROWS |
+----------------+------------+
| p2020          |          1 |
| p2021          |          2 |
| p2022          |          2 |
| p2023          |          2 |
| pFuture        |          1 |
+----------------+------------+

Вы успешно запросили данные из конкретных секций и наблюдали, как MySQL использует секционирование для оптимизации запросов.

Реорганизация и управление секциями

На этом шаге мы научимся изменять структуру секционирования существующей таблицы с помощью оператора ALTER TABLE. Это полезно для адаптации схемы секционирования по мере роста данных или изменения ваших потребностей.

Напоминание: Вы все еще должны находиться в оболочке MySQL и использовать базу данных sales_data. Если это не так, используйте:

USE sales_data;

Предположим, мы хотим добавить новую секцию для 2024 года. В настоящее время данные за 2024 год и далее находятся в секции pFuture. Вы не можете добавить новую секцию с помощью ADD PARTITION, потому что секция pFuture определена с VALUES LESS THAN MAXVALUE, которая всегда должна быть последней секцией.

Вместо этого нам нужно REORGANIZE секцию pFuture, чтобы разделить ее. Мы разделим pFuture на две новые секции: одну для 2024 года (p2024) и новую секцию pFuture для всего, что после нее.

ALTER TABLE sales REORGANIZE PARTITION pFuture INTO (
    PARTITION p2024 VALUES LESS THAN (2025),
    PARTITION pFuture VALUES LESS THAN MAXVALUE
);

Эта команда берет существующую секцию pFuture, перемещает все данные за 2024 год в новую секцию p2024 и переопределяет pFuture для охвата дат с 2025 года и далее. Строка с sale_date '2024-01-01' будет перемещена в p2024.

Давайте проверим обновленную структуру секций и количество строк:

SELECT
    PARTITION_NAME,
    TABLE_ROWS
FROM
    INFORMATION_SCHEMA.PARTITIONS
WHERE
    TABLE_SCHEMA = 'sales_data' AND TABLE_NAME = 'sales';

Вы должны увидеть новую секцию p2024. Строка за 2024 год теперь находится в p2024.

+----------------+------------+
| PARTITION_NAME | TABLE_ROWS |
+----------------+------------+
| p2020          |          0 |
| p2021          |          2 |
| p2022          |          2 |
| p2023          |          2 |
| p2024          |          0 |
| pFuture        |          0 |
+----------------+------------+

Теперь давайте продемонстрируем слияние секций. Предположим, мы хотим объединить секции p2020 и p2021 в одну секцию с именем p2020_2021.

ALTER TABLE sales REORGANIZE PARTITION p2020, p2021 INTO (
    PARTITION p2020_2021 VALUES LESS THAN (2022)
);

Эта команда объединяет данные из p2020 и p2021 в новую секцию под названием p2020_2021. Предложение VALUES LESS THAN (2022) определяет новую границу для этой объединенной секции.

Снова проверьте структуру секций:

SELECT
    PARTITION_NAME,
    TABLE_ROWS
FROM
    INFORMATION_SCHEMA.PARTITIONS
WHERE
    TABLE_SCHEMA = 'sales_data' AND TABLE_NAME = 'sales';

Вы увидите, что p2020 и p2021 исчезли, а p2020_2021 существует с объединенным количеством строк.

+----------------+------------+
| PARTITION_NAME | TABLE_ROWS |
+----------------+------------+
| p2020_2021     |          3 |
| p2022          |          2 |
| p2023          |          2 |
| p2024          |          0 |
| pFuture        |          0 |
+----------------+------------+

Наконец, давайте удалим секцию. Мы можем удалить секцию p2024. Обратите внимание, что это также удалит все данные внутри этой секции.

ALTER TABLE sales DROP PARTITION p2024;

Проверьте структуру секций в последний раз:

SELECT
    PARTITION_NAME,
    TABLE_ROWS
FROM
    INFORMATION_SCHEMA.PARTITIONS
WHERE
    TABLE_SCHEMA = 'sales_data' AND TABLE_NAME = 'sales';

Секция p2024 больше не должна быть в списке.

+----------------+------------+
| PARTITION_NAME | TABLE_ROWS |
+----------------+------------+
| p2020_2021     |          3 |
| p2022          |          2 |
| p2023          |          2 |
| pFuture        |          0 |
+----------------+------------+

Вы успешно реорганизовали, объединили и удалили секции с помощью ALTER TABLE. Это демонстрирует гибкость управления секционированными таблицами по мере развития ваших данных.

Проверка влияния секций на скорость запросов

На этом шаге мы рассмотрим, как секционирование может влиять на производительность запросов. Хотя наш текущий набор данных невелик, мы все же можем наблюдать принцип отсечения секций (partition pruning), при котором MySQL сканирует только необходимые секции. Для больших наборов данных этот эффект гораздо более выражен.

Напоминание: Вы все еще должны находиться в оболочке MySQL и использовать базу данных sales_data. Если это не так, используйте:

USE sales_data;

Чтобы наблюдать влияние секционирования, мы можем использовать оператор EXPLAIN, который показывает план выполнения запроса. В частности, EXPLAIN PARTITIONS покажет, к каким секциям осуществляется доступ.

Выполним запрос, который фильтрует по ключу секционирования (году sale_date):

EXPLAIN PARTITIONS SELECT * FROM sales WHERE sale_date >= '2023-01-01' AND sale_date < '2024-01-01';

Обратите внимание на столбец partitions в выводе. Он должен указывать, что сканируется только секция p2023.

Теперь выполним запрос, который фильтрует не напрямую по ключу секционирования, а по другому столбцу (amount):

EXPLAIN PARTITIONS SELECT * FROM sales WHERE amount > 200;

В этом случае, поскольку условие запроса не относится напрямую к ключу секционирования (sale_date), MySQL может потребоваться сканировать несколько или все секции для поиска соответствующих строк. Столбец partitions в выводе EXPLAIN PARTITIONS покажет, какие секции были рассмотрены. При нашем небольшом наборе данных он может по-прежнему сканировать все секции.

Чтобы получить более подробное представление о процессе выполнения запроса и затраченном времени, вы можете использовать функцию профилирования MySQL.

Включить профилирование:

SET profiling = 1;

Теперь снова выполните два запроса:

SELECT * FROM sales WHERE sale_date >= '2023-01-01' AND sale_date < '2024-01-01';
SELECT * FROM sales WHERE amount > 200;

Просмотрите результаты профилирования:

SHOW PROFILES;

Вывод перечислит выполненные запросы и их продолжительность. Затем вы можете изучить детали конкретного запроса, используя его Query_ID:

SHOW PROFILE FOR QUERY [Query_ID];

Замените [Query_ID] на идентификатор запроса, который вы хотите проанализировать, из вывода SHOW PROFILES. Обратите внимание на различные этапы выполнения и время, затраченное на каждый из них.

Хотя разница во времени может быть незначительной при нашем небольшом наборе данных, в реальных сценариях с миллионами строк запросы, которые могут использовать отсечение секций (например, запрос, фильтрующий по YEAR(sale_date)), будут значительно быстрее, чем те, которым необходимо сканировать несколько или все секции.

Наконец, отключите профилирование:

SET profiling = 0;

Этот шаг продемонстрировал, как использовать EXPLAIN PARTITIONS и профилирование для понимания того, как секционирование влияет на выполнение запросов и их производительность.

Резюме

В этой лабораторной работе вы научились реализовывать секционирование в MySQL для больших наборов данных с целью улучшения производительности запросов и управления данными. Вы начали с создания базы данных и таблицы, секционированной по диапазону на основе года столбца даты. Затем вы практиковались в запросе данных из конкретных секций, наблюдая, как MySQL использует отсечение секций (partition pruning) для оптимизации запросов. Наконец, вы узнали, как реорганизовывать секции, добавляя, разделяя и объединяя их с помощью оператора ALTER TABLE, а также изучили, как использовать EXPLAIN PARTITIONS и профилирование для понимания влияния секционирования на скорость запросов. Секционирование — это мощный метод для эффективного управления большими таблицами и выполнения запросов к ним в MySQL.

После завершения всех шагов вы можете выйти из оболочки MySQL, набрав:

exit;