Перейти к основному содержимому
Версия: 2.0.x

SQL Plan Manager

Эта тема знакомит с базовыми концепциями и сценариями использования функции SQL Plan Manager, а также с тем, как использовать SQL Plan Manager для регуляризации планов запросов.

Начиная с версии v1.5.2, Selena поддерживает функцию SQL Plan Manager.

Обзор

SQL Plan Manager позволяет пользователям привязывать план запроса к запросу, тем самым предотвращая изменение плана запроса из-за изменений состояния системы (в основном обновлений данных и обновлений статистики), стабилизируя производительность запросов.

Рабочий процесс

SQL Plan Manager требует предварительного указания привязанного SQL запроса и плана запроса (Baseline), который будет использоваться. Здесь запрос относится к фактическому SQL запросу, выполняемому пользователем, в то время как план запроса - это SQL запрос, оптимизированный вручную или с добавленными hints.

Рабочий процесс SQL Plan Manager выглядит следующим образом:

  1. Создание Baseline: Используйте команду CREATE BASELINE для привязки плана запроса к указанному SQL запросу.
  2. Переписывание запроса: Запросы, отправленные в Selena, автоматически сопоставляются с Baselines, хранящимися в SQL Plan Manager. Если они успешно совпадают, план запроса из Baseline используется для запроса.

Примечания по созданию Baseline:

  • Вы должны обеспечить логическую согласованность между привязанным SQL и SQL плана выполнения в Baseline. Selena выполняет базовые проверки таблиц и параметров, но не может гарантировать полную проверку логической согласованности. Обеспечение логической корректности плана выполнения - это ответственность самих пользователей.
  • По умолчанию привязанный SQL в Baseline хранит свой собственный SQL fingerprint. По умолчанию постоянные значения в SQL будут заменены на переменные параметры (например, изменение t1.v1 > 1000 на t1.v1 > ?) для улучшения сопоставления SQL.
  • План выполнения, привязанный в Baseline, может быть настроен путем изменения логики SQL или добавления Hints (Join Hints или Set_Var) для обеспечения генерации желаемого плана выполнения.
  • Для сложных SQL Selena может не автоматически привязывать SQL и план выполнения в Baseline. В таких случаях можно использовать ручную привязку, как подробно описано в разделе Расширенное использование.

Примечания по переписыванию запросов:

  • SQL Plan Manager в первую очередь полагается на сопоставление SQL fingerprint. Он проверяет, совпадает ли SQL fingerprint запроса с fingerprint Baseline. Если запрос совпадает с Baseline, параметры в запросе автоматически подставляются в план выполнения Baseline.
  • В процессе сопоставления, если запрос совпадает с несколькими Baselines со статусом enable, оптимизатор оценивает и выбирает оптимальный Baseline.
  • В процессе сопоставления SQL Plan Manager проверяет, соответствует ли Baseline запросу. Если сопоставление не удается, план запроса Baseline не будет использован.
  • Для планов выполнения, переписанных SQL Plan Manager, оператор EXPLAIN вернет Using baseline plan[id].

Управление Baseline

Создание Baseline

Синтаксис:

CREATE [GLOBAL] BASELINE [ON <BindSQL>] USING <PlanSQL>
[PROPERTIES ("key" = "value"[, ...])]

Параметры:

  • GLOBAL: (Опционально) Создает Baseline глобального уровня.
  • BindSQL: (Опционально) Конкретный запрос, который будет привязан к запросу Baseline (плану выполнения). Если этот параметр не указан, запрос Baseline привязывается к самому себе и использует свой собственный план запроса.
  • PlanSQL: Запрос, используемый для определения плана выполнения.

Примеры:

-- Создать BASELINE уровня сессии, напрямую привязав SQL Baseline к самому себе и используя его собственный план запроса.
CREATE BASELINE
USING SELECT t1.v2, t2.v3 FROM t1 JOIN t2 ON t1.v2 = t2.v2 WHERE t1.v2 > 100;

-- Создать BASELINE глобального уровня, напрямую привязав SQL Baseline к самому себе и используя его собственный план запроса с указанными Join Hints.
CREATE GLOBAL BASELINE
USING SELECT t1.v2, t2.v3 FROM t1 JOIN[BROADCAST] t2 ON t1.v2 = t2.v2 WHERE t1.v2 > 100;

-- Создать BASELINE уровня сессии, привязав запрос к SQL Baseline и используя план запроса SQL Baseline с указанными Join Hints.
CREATE BASELINE ON SELECT t1.v2, t2.v3 FROM t1, t2 WHERE t1.v2 = t2.v2 AND t1.v2 > 100
USING SELECT t1.v2, t2.v3 FROM t1 JOIN[BROADCAST] t2 on t1.v2 = t2.v2 where t1.v2 > 100;

Просмотр Baseline

Синтаксис:

SHOW BASELINE [WHERE <condition>]

SHOW BASELINE [ON <query>]

Пример:

MySQL > show baseline\G;
***************************[ 1. row ]***************************
Id | 646125
global | N
enable | N
bindSQLDigest | SELECT * FROM `td`.`t2` INNER JOIN `td`.`t1` ON `td`.`t2`.`v2` = `td`.`t1`.`v2` LIMIT 2
bindSQLHash | 1085294
bindSQL | SELECT * FROM `td`.`t2` INNER JOIN `td`.`t1` ON `td`.`t2`.`v2` = `td`.`t1`.`v2` LIMIT 2
planSQL | SELECT t2.v1 AS c_1, t2.v2 AS c_2, t2.v3 AS c_3, t1.v1 AS c_4, t1.v2 AS c_5 FROM t2 INNER JOIN[SHUFFLE] t1 ON t2.v2 = t1.v2 LIMIT 2
costs | 582.0
queryMs | -1.0
source | USER
updateTime | 2025-05-16 14:50:45
***************************[ 2. row ]***************************
Id | 636134
global | Y
enable | Y
bindSQLDigest | SELECT * FROM `td`.`t2` INNER JOIN `td`.`t1` ON `td`.`t2`.`v2` = `td`.`t1`.`v2` WHERE `td`.`t2`.`v3` = ?
bindSQLHash | 1085294
bindSQL | SELECT * FROM `td`.`t2` INNER JOIN `td`.`t1` ON `td`.`t2`.`v2` = `td`.`t1`.`v2` WHERE `td`.`t2`.`v3` = _spm_const_range(1, 10, 20)
planSQL | SELECT t_0.v1 AS c_1, t_0.v2 AS c_2, t_0.v3 AS c_3, t1.v1 AS c_4, t1.v2 AS c_5 FROM (SELECT * FROM t2 WHERE v3 = _spm_const_range(1, 10, 20)) t_0 INNER JOIN[SHUFFLE] t1 ON t_0.v2 = t1.v2
costs | 551.0204081632653
queryMs | -1.0
source | USER
updateTime | 2025-05-13 15:29:04
2 rows in set
Time: 0.019s

MySQL > show baseline where global = true\G;
***************************[ 1. row ]***************************
Id | 636134
global | Y
enable | Y
bindSQLDigest | SELECT * FROM `td`.`t2` INNER JOIN `td`.`t1` ON `td`.`t2`.`v2` = `td`.`t1`.`v2` WHERE `td`.`t2`.`v3` = ?
bindSQLHash | 1085294
bindSQL | SELECT * FROM `td`.`t2` INNER JOIN `td`.`t1` ON `td`.`t2`.`v2` = `td`.`t1`.`v2` WHERE `td`.`t2`.`v3` = _spm_const_range(1, 10, 20)
planSQL | SELECT t_0.v1 AS c_1, t_0.v2 AS c_2, t_0.v3 AS c_3, t1.v1 AS c_4, t1.v2 AS c_5 FROM (SELECT * FROM t2 WHERE v3 = _spm_const_range(1, 10, 20)) t_0 INNER JOIN[SHUFFLE] t1 ON t_0.v2 = t1.v2
costs | 551.0204081632653
queryMs | -1.0
source | USER
updateTime | 2025-05-13 15:29:04
1 row in set
Time: 0.013s

MySQL > show baseline on SELECT count(1) AS `count(1)` FROM `old`.`t1` INNER JOIN `old`.`t2` ON `old`.`t1`.`k2` = `old`.`t2`.`k2` LIMIT 10\G;
***************************[ 1. row ]***************************
Id | 679817
global | Y
enable | Y
bindSQLDigest | SELECT count(?) AS `count(1)` FROM `old`.`t1` INNER JOIN `old`.`t2` ON `old`.`t1`.`k2` = `old`.`t2`.`k2` LIMIT 10
bindSQLHash | 1085927
bindSQL | SELECT count(_spm_const_var(1)) AS `count(1)` FROM `old`.`t1` INNER JOIN `old`.`t2` ON `old`.`t1`.`k2` = `old`.`t2`.`k2` LIMIT 10
planSQL | SELECT count(_spm_const_var(1)) AS c_7 FROM (SELECT 1 AS c_9 FROM t1 INNER JOIN[SHUFFLE] t2 ON t1.k2 = t2.k2) t_0 LIMIT 10
costs | 2532.6
queryMs | 35.0
source | CAPTURE
updateTime | 2025-05-27 11:17:48
1 row in set
Time: 0.026s

Удаление Baseline

Синтаксис:

DROP BASELINE <id>,<id>...

Параметр:

id: ID Baseline. Вы можете получить ID Baseline, выполнив SHOW BASELINE.

Пример:

-- Удалить Baseline с ID 140035.
DROP BASELINE 140035;

Включение/Отключение Baseline

Синтаксис:

ENABLE BASELINE <id>,<id>...
DISABLE BASELINE <id>,<id>...

Параметр:

id: ID Baseline. Вы можете получить ID Baseline, выполнив SHOW BASELINE.

Пример:

-- включить baseline с id 140035
ENABLE BASELINE 140035;
-- отключить baseline с id 140035 140037
DISABLE BASELINE 140035, 140037;

Переписывание запросов

Вы можете включить функцию переписывания запросов SQL Plan Manager, установив переменную enable_spm_rewrite в true.

SET enable_spm_rewrite = true;

После привязки плана выполнения Selena автоматически перепишет соответствующий запрос в соответствующий план запроса.

Пример:

Проверьте исходный SQL и план выполнения:

mysql> EXPLAIN SELECT t1.v2, t2.v3 FROM t1, t2 WHERE t1.v2 = t2.v2 AND t1.v1 > 20;
+-----------------------------------------+
| Explain String |
+-----------------------------------------+
| PLAN FRAGMENT 0 |
| OUTPUT EXPRS:2: v2 | 5: v3 |
| PARTITION: UNPARTITIONED |
......
| 5:HASH JOIN |
| | join op: INNER JOIN (PARTITIONED) |
| | colocate: false, reason: |
| | equal join conjunct: 4: v2 = 2: v2 |
| | |
| |----4:EXCHANGE |
| | |
| 1:EXCHANGE |
......

Создайте Baseline для привязки исходного SQL к плану выполнения SQL с Join Hints:

MySQL td> create global baseline on select t1.v2, t2.v3 from t1, t2 where t1.v2 = t2.v2 and t1.v1 > 1000
using select t1.v2, t2.v3 from t1 join[broadcast] t2 on t1.v2 = t2.v2 where t1.v1 > 1000;
Query OK, 0 rows affected
Time: 0.074s
MySQL td> show baseline\G;
***************************[ 1. row ]***************************
Id | 647139
global | Y
enable | Y
bindSQLDigest | SELECT `td`.`t1`.`v2`, `td`.`t2`.`v3` FROM `td`.`t1` , `td`.`t2` WHERE (`td`.`t1`.`v2` = `td`.`t2`.`v2`) AND (`td`.`t1`.`v1` > ?)
bindSQLHash | 1085294
bindSQL | SELECT `td`.`t1`.`v2`, `td`.`t2`.`v3` FROM `td`.`t1` , `td`.`t2` WHERE (`td`.`t1`.`v2` = `td`.`t2`.`v2`) AND (`td`.`t1`.`v1` > _spm_const_var(1))
planSQL | SELECT t_0.v2 AS c_2, t2.v3 AS c_5 FROM (SELECT v2 FROM t1 WHERE v1 > _spm_const_var(1)) t_0 INNER JOIN[BROADCAST] t2 ON t_0.v2 = t2.v2
costs | 1193.0
queryMs | -1.0
source | USER
updateTime | 2025-05-16 15:51:36
1 rows in set
Time: 0.016s

Включите переписывание запросов SQL Plan Manager и проверьте, переписан ли исходный запрос с помощью Baseline:

MySQL td> show variables like '%enable_spm_re%'
+--------------------+-------+
| Variable_name | Value |
+--------------------+-------+
| enable_spm_rewrite | false |
+--------------------+-------+
1 row in set
Time: 0.007s
MySQL td> set enable_spm_rewrite=true
Query OK, 0 rows affected
Time: 0.001s
MySQL td> explain select t1.v2, t2.v3 from t1, t2 where t1.v2 = t2.v2 and t1.v1 > 20;
+-----------------------------------------+
| Explain String |
+-----------------------------------------+
| Using baseline plan[647139] |
| |
| PLAN FRAGMENT 0 |
| OUTPUT EXPRS:2: v2 | 5: v3 |
.............
| 4:HASH JOIN |
| | join op: INNER JOIN (BROADCAST) |
| | colocate: false, reason: |
| | equal join conjunct: 2: v2 = 4: v2 |
| | |
| |----3:EXCHANGE |
| | |
| 1:Project |
| | <slot 2> : 2: v2 |
.............

Расширенное использование

Для следующих сценариев вы можете попробовать использовать ручную привязку плана запроса:

  • Для сложных SQL, SQL Plan Manager не может автоматически привязать SQL и план запроса.
  • Для конкретных сценариев (например, фиксированные параметры или условные параметры) автоматическая привязка не может удовлетворить требования.

По сравнению с автоматической привязкой, ручная привязка предлагает большую гибкость, но требует понимания некоторых механизмов выполнения SQL Plan Manager.

Логика выполнения

Функции SQL Plan Manager

Функции SQL Plan Manager - это функции-заполнители в SQL Plan Manager с двумя основными целями:

  • Пометить выражения в параметризованном SQL для последующего извлечения и замены параметров в процессе.
  • Проверить условия параметров, сопоставляя SQL с различными параметрами с различными планами запросов через условия параметров.

В настоящее время Selena поддерживает следующие функции SQL Plan Manager:

  • _spm_const_var(placeholdID): Используется для пометки одного постоянного значения.
  • _spm_const_list(placeholdID): Используется для пометки нескольких постоянных значений, обычно используется для пометки нескольких постоянных значений в условии IN.
  • _spm_const_range(placeholdID, min, max): Используется для пометки одного постоянного значения, но требует, чтобы постоянное значение находилось в указанном диапазоне '[min, max]'.
  • _spm_const_num(placeholdID, value...): Используется для пометки одного постоянного значения, но требует, чтобы постоянное значение было значением в указанном перечислении 'value...'.

placeholdID - это целое число, которое используется как уникальный идентификатор для параметра, который используется при привязке Baseline и генерации плана.

Процесс создания Baseline

  1. Выполните CREATE BASELINE для получения привязываемого BindSQL и запроса плана выполнения PlanSQL.
  2. Параметризуйте BindSQL: Замените литеральные значения или выражения функциями SQL Plan Manager. Например, замените id > 200 на id > _spm_const_var(0), где параметр 0 - это ID заполнителя, используемый для подтверждения позиции выражения в BindSQL и PlanSQL. Для получения дополнительных функций SQL Plan Manager см. Функции SQL Plan Manager.
  3. Привяжите заполнители в PlanSQL: Найдите позицию заполнителей в PlanSQL и замените их исходными выражениями.
  4. Используйте оптимизатор для оптимизации PlanSQL и получения плана запроса.
  5. Сериализуйте план запроса в SQL с Hints.
  6. Сохраните Baseline (SQL fingerprint BindSQL, оптимизированный SQL плана выполнения).

Процесс переписывания запроса

Логика переписывания запроса похожа на оператор PREPARE.

  1. Выполните запрос.
  2. Нормализуйте запрос в SQL fingerprint.
  3. Используйте SQL fingerprint для поиска Baseline (сопоставление с BindSQL Baseline).
  4. Привяжите запрос к Baseline, проверьте, соответствует ли запрос BindSQL Baseline, и извлеките соответствующие значения параметров из запроса, используя функции SQL Plan Manager в BindSQL. Например, id > 1000 в запросе привязывается к id > _spm_const_var(0) в BindSQL, извлекая _spm_const_var(0) = 1000.
  5. Замените параметры SQL Plan Manager в PlanSQL Baseline.
  6. Верните PlanSQL для замены исходного запроса.

Ручная привязка запроса

Вы можете использовать функции SQL Plan Manager для привязки более сложных SQL к Baselines.

Пример 1

Например, SQL для привязки выглядит следующим образом:

create global baseline using
with ss as (
select i_item_id, sum(ss_ext_sales_price) total_sales
from store_sales, item
where i_color in ('slate', 'blanched', 'burnished') and ss_item_sk = i_item_sk
group by i_item_id
),
cs as (
select i_item_id, sum(cs_ext_sales_price) total_sales
from catalog_sales, item
where i_color in ('slate', 'blanched', 'burnished') and cs_item_sk = i_item_sk
group by i_item_id
)
select i_item_id, sum(total_sales) total_sales
from ( select * from ss
union all
select * from cs) tmp1
group by i_item_id;

Поскольку постоянные значения в i_color in ('slate', 'blanched', 'burnished') одинаковы, SQL будет распознан с SQL Plan Manager как:

with ss as (
select i_item_id, sum(ss_ext_sales_price) total_sales
from store_sales, item
where i_color IN (_spm_const_list(1)) and ss_item_sk = i_item_sk
group by i_item_id
),
cs as (
select i_item_id, sum(cs_ext_sales_price) total_sales
from catalog_sales, item
where i_color IN (_spm_const_list(1)) and cs_item_sk = i_item_sk
group by i_item_id
)
select i_item_id, sum(total_sales) total_sales
from ( select * from ss
union all
select * from cs) tmp1
group by i_item_id;

Это означает, что оба экземпляра i_color in ('xxx', 'xxx') распознаются как один и тот же параметр, что делает невозможным для SQL Plan Manager их различение, когда в SQL используются разные параметры.

-- can be bind baseline
MySQL tpcds> explain with ss as (
select i_item_id, sum(ss_ext_sales_price) total_sales
from store_sales, item
where i_color in ('A', 'B', 'C') and ss_item_sk = i_item_sk
group by i_item_id
),
cs as (
select i_item_id, sum(cs_ext_sales_price) total_sales
from catalog_sales, item
where i_color in ('A', 'B', 'C') and cs_item_sk = i_item_sk
group by i_item_id
)
select i_item_id, sum(total_sales) total_sales
from ( select * from ss
union all
select * from cs) tmp1
group by i_item_id;
+-------------------------------------------------------------------------------------------+
| Explain String |
+-------------------------------------------------------------------------------------------+
| Using baseline plan[646215] |
| |
| PLAN FRAGMENT 0 |
| OUTPUT EXPRS:104: i_item_id | 106: sum |
| PARTITION: UNPARTITIONED |
................................ |
| avgRowSize=3.0 |
+-------------------------------------------------------------------------------------------+
184 rows in set
Time: 0.095s

-- can't bind basline
MySQL tpcds> explain with ss as (
select i_item_id, sum(ss_ext_sales_price) total_sales
from store_sales, item
where i_color in ('A', 'B', 'C') and ss_item_sk = i_item_sk
group by i_item_id
),
cs as (
select i_item_id, sum(cs_ext_sales_price) total_sales
from catalog_sales, item
where i_color in ('E', 'F', 'G') and cs_item_sk = i_item_sk
group by i_item_id
)
select i_item_id, sum(total_sales) total_sales
from ( select * from ss
union all
select * from cs) tmp1
group by i_item_id;
+-------------------------------------------------------------------------------------------+
| Explain String |
+-------------------------------------------------------------------------------------------+
| PLAN FRAGMENT 0 |
| OUTPUT EXPRS:104: i_item_id | 106: sum |
| PARTITION: UNPARTITIONED |
................................ |
| avgRowSize=3.0 |
+-------------------------------------------------------------------------------------------+
182 rows in set
Time: 0.040s

В таких случаях вы можете вручную указать параметры в BindSQL и PlanSQL:

create global baseline using
with ss as (
select i_item_id, sum(ss_ext_sales_price) total_sales
from store_sales, item
where i_color IN (_spm_const_list(1)) and ss_item_sk = i_item_sk
group by i_item_id
),
cs as (
select i_item_id, sum(cs_ext_sales_price) total_sales
from catalog_sales, item
where i_color IN (_spm_const_list(2)) and cs_item_sk = i_item_sk
group by i_item_id
)
select i_item_id, sum(total_sales) total_sales
from ( select * from ss
union all
select * from cs) tmp1
group by i_item_id;

Проверьте, переписан ли запрос с помощью Baseline:

MySQL td> show baseline\G;
***************************[ 1. row ]***************************
Id | 646215
global | Y
enable | Y
bindSQLDigest | WITH `ss` (`i_item_id`, `total_sales`) AS (SELECT `tpcds`.`item`.`i_item_id`, sum(`tpcds`.`store_sales`.`ss_ext_sales_price`) AS `total_sales` FROM `tpcds`.`store_sales` , `tpcds`.`item` WHERE (`tpcds`.`item`.`i_color` IN (?)) AND (`tpcds`.`store_sales`.`ss_item_sk` = `tpcds`.`item`.`i_item_sk`) GROUP BY `tpcds`.`item`.`i_item_id`) , `cs` (`i_item_id`, `total_sales`) AS (SELECT `tpcds`.`item`.`i_item_id`, sum(`tpcds`.`catalog_sales`.`cs_ext_sales_price`) AS `total_sales` FROM `tpcds`.`catalog_sales` , `tpcds`.`item` WHERE (`tpcds`.`item`.`i_color` IN (?)) AND (`tpcds`.`catalog_sales`.`cs_item_sk` = `tpcds`.`item`.`i_item_sk`) GROUP BY `tpcds`.`item`.`i_item_id`) SELECT `tmp1`.`i_item_id`, sum(`tmp1`.`total_sales`) AS `total_sales` FROM (SELECT * FROM `ss` UNION ALL SELECT * FROM `cs`) `tmp1` GROUP BY `tmp1`.`i_item_id`
bindSQLHash | 203487418
bindSQL | WITH `ss` (`i_item_id`, `total_sales`) AS (SELECT `tpcds`.`item`.`i_item_id`, sum(`tpcds`.`store_sales`.`ss_ext_sales_price`) AS `total_sales` FROM `tpcds`.`store_sales` , `tpcds`.`item` WHERE (`tpcds`.`item`.`i_color` IN (_spm_const_list(1))) AND (`tpcds`.`store_sales`.`ss_item_sk` = `tpcds`.`item`.`i_item_sk`) GROUP BY `tpcds`.`item`.`i_item_id`) , `cs` (`i_item_id`, `total_sales`) AS (SELECT `tpcds`.`item`.`i_item_id`, sum(`tpcds`.`catalog_sales`.`cs_ext_sales_price`) AS `total_sales` FROM `tpcds`.`catalog_sales` , `tpcds`.`item` WHERE (`tpcds`.`item`.`i_color` IN (_spm_const_list(1))) AND (`tpcds`.`catalog_sales`.`cs_item_sk` = `tpcds`.`item`.`i_item_sk`) GROUP BY `tpcds`.`item`.`i_item_id`) SELECT `tmp1`.`i_item_id`, sum(`tmp1`.`total_sales`) AS `total_sales` FROM (SELECT * FROM `ss` UNION ALL SELECT * FROM `cs`) `tmp1` GROUP BY `tmp1`.`i_item_id`
planSQL | SELECT c_104, sum(c_105) AS c_106 FROM (SELECT * FROM (SELECT i_item_id AS c_104, c_46 AS c_105 FROM (SELECT i_item_id, sum(ss_ext_sales_price) AS c_46 FROM (SELECT i_item_id, ss_ext_sales_price FROM store_sales INNER JOIN[BROADCAST] (SELECT i_item_sk, i_item_id FROM item WHERE i_color IN (_spm_const_list(1))) t_0 ON ss_item_sk = i_item_sk) t_1 GROUP BY i_item_id) t_2 UNION ALL SELECT i_item_id AS c_104, c_103 AS c_105 FROM (SELECT i_item_id, sum(cs_ext_sales_price) AS c_103 FROM (SELECT i_item_id, cs_ext_sales_price FROM catalog_sales INNER JOIN[BROADCAST] (SELECT i_item_sk, i_item_id FROM item WHERE i_color IN (_spm_const_list(1))) t_3 ON cs_item_sk = i_item_sk) t_4 GROUP BY i_item_id) t_5) t_6) t_7 GROUP BY c_104
costs | 2.608997082E8
queryMs | -1.0
source | USER
updateTime | 2025-05-16 15:30:29
***************************[ 2. row ]***************************
Id | 646237
global | Y
enable | Y
bindSQLDigest | WITH `ss` (`i_item_id`, `total_sales`) AS (SELECT `tpcds`.`item`.`i_item_id`, sum(`tpcds`.`store_sales`.`ss_ext_sales_price`) AS `total_sales` FROM `tpcds`.`store_sales` , `tpcds`.`item` WHERE (`tpcds`.`item`.`i_color` IN (?)) AND (`tpcds`.`store_sales`.`ss_item_sk` = `tpcds`.`item`.`i_item_sk`) GROUP BY `tpcds`.`item`.`i_item_id`) , `cs` (`i_item_id`, `total_sales`) AS (SELECT `tpcds`.`item`.`i_item_id`, sum(`tpcds`.`catalog_sales`.`cs_ext_sales_price`) AS `total_sales` FROM `tpcds`.`catalog_sales` , `tpcds`.`item` WHERE (`tpcds`.`item`.`i_color` IN (?)) AND (`tpcds`.`catalog_sales`.`cs_item_sk` = `tpcds`.`item`.`i_item_sk`) GROUP BY `tpcds`.`item`.`i_item_id`) SELECT `tmp1`.`i_item_id`, sum(`tmp1`.`total_sales`) AS `total_sales` FROM (SELECT * FROM `ss` UNION ALL SELECT * FROM `cs`) `tmp1` GROUP BY `tmp1`.`i_item_id`
bindSQLHash | 203487418
bindSQL | WITH `ss` (`i_item_id`, `total_sales`) AS (SELECT `tpcds`.`item`.`i_item_id`, sum(`tpcds`.`store_sales`.`ss_ext_sales_price`) AS `total_sales` FROM `tpcds`.`store_sales` , `tpcds`.`item` WHERE (`tpcds`.`item`.`i_color` IN (_spm_const_list(1))) AND (`tpcds`.`store_sales`.`ss_item_sk` = `tpcds`.`item`.`i_item_sk`) GROUP BY `tpcds`.`item`.`i_item_id`) , `cs` (`i_item_id`, `total_sales`) AS (SELECT `tpcds`.`item`.`i_item_id`, sum(`tpcds`.`catalog_sales`.`cs_ext_sales_price`) AS `total_sales` FROM `tpcds`.`catalog_sales` , `tpcds`.`item` WHERE (`tpcds`.`item`.`i_color` IN (_spm_const_list(2))) AND (`tpcds`.`catalog_sales`.`cs_item_sk` = `tpcds`.`item`.`i_item_sk`) GROUP BY `tpcds`.`item`.`i_item_id`) SELECT `tmp1`.`i_item_id`, sum(`tmp1`.`total_sales`) AS `total_sales` FROM (SELECT * FROM `ss` UNION ALL SELECT * FROM `cs`) `tmp1` GROUP BY `tmp1`.`i_item_id`
planSQL | SELECT c_104, sum(c_105) AS c_106 FROM (SELECT * FROM (SELECT i_item_id AS c_104, c_46 AS c_105 FROM (SELECT i_item_id, sum(ss_ext_sales_price) AS c_46 FROM (SELECT i_item_id, ss_ext_sales_price FROM store_sales INNER JOIN[BROADCAST] (SELECT i_item_sk, i_item_id FROM item WHERE i_color IN (_spm_const_list(1))) t_0 ON ss_item_sk = i_item_sk) t_1 GROUP BY i_item_id) t_2 UNION ALL SELECT i_item_id AS c_104, c_103 AS c_105 FROM (SELECT i_item_id, sum(cs_ext_sales_price) AS c_103 FROM (SELECT i_item_id, cs_ext_sales_price FROM catalog_sales INNER JOIN[BROADCAST] (SELECT i_item_sk, i_item_id FROM item WHERE i_color IN (_spm_const_list(2))) t_3 ON cs_item_sk = i_item_sk) t_4 GROUP BY i_item_id) t_5) t_6) t_7 GROUP BY c_104
costs | 2.635637082E8
queryMs | -1.0
source | USER
updateTime | 2025-05-16 15:37:35
2 rows in set
Time: 0.013s
MySQL td> explain with ss as (
select i_item_id, sum(ss_ext_sales_price) total_sales
from store_sales, item
where i_color in ('A', 'B', 'C') and ss_item_sk = i_item_sk
group by i_item_id
),
cs as (
select i_item_id, sum(cs_ext_sales_price) total_sales
from catalog_sales, item
where i_color in ('E', 'F', 'G') and cs_item_sk = i_item_sk
group by i_item_id
)
select i_item_id, sum(total_sales) total_sales
from ( select * from ss
union all
select * from cs) tmp1
group by i_item_id;
+-------------------------------------------------------------------------------------------+
| Explain String |
+-------------------------------------------------------------------------------------------+
| Using baseline plan[646237] |
| |
| PLAN FRAGMENT 0 |
| OUTPUT EXPRS:104: i_item_id | 106: sum |
| PARTITION: UNPARTITIONED |
......

Из приведенного выше вывода видно, что запрос переписан с помощью Baseline с различными параметрами в функциях SQL Plan Manager.

Пример 2

Для следующего запроса, если вы хотите использовать различный baseline для разных i_color

select i_item_id, sum(ss_ext_sales_price) total_sales
from store_sales join item on ss_item_sk = i_item_sk
where i_color = 25
group by i_item_id

Вы можете использовать '_spm_const_range' для:

-- 10 <= i_color <= 50 время, используется SHUFFLE JOIN
MySQL tpcds> create baseline using select i_item_id, sum(ss_ext_sales_price) total_sales
from store_sales join[SHUFFLE] item on ss_item_sk = i_item_sk
where i_color = _spm_const_range(1, 10, 50)
group by i_item_id
Query OK, 0 rows affected
Time: 0.017s
-- i_color для 60,70,80 время, используется BROADCAST JOIN
MySQL tpcds> create baseline using select i_item_id, sum(ss_ext_sales_price) total_sales
from store_sales join[BROADCAST] item on ss_item_sk = i_item_sk
where i_color = _spm_const_enum(1, 60, 70, 80)
group by i_item_id
Query OK, 0 rows affected
Time: 0.009s
MySQL tpcds> show baseline\G;
***************************[ 1. row ]***************************
Id | 647167
global | N
enable | Y
bindSQLDigest | SELECT `tpcds`.`item`.`i_item_id`, sum(`tpcds`.`store_sales`.`ss_ext_sales_price`) AS `total_sales` FROM `tpcds`.`store_sales` INNER JOIN `tpcds`.`item` ON `tpcds`.`store_sales`.`ss_item_sk` = `tpcds`.`item`.`i_item_sk` WHERE `tpcds`.`item`.`i_color` = ? GROUP BY `tpcds`.`item`.`i_item_id`
bindSQLHash | 68196091
bindSQL | SELECT `tpcds`.`item`.`i_item_id`, sum(`tpcds`.`store_sales`.`ss_ext_sales_price`) AS `total_sales` FROM `tpcds`.`store_sales` INNER JOIN `tpcds`.`item` ON `tpcds`.`store_sales`.`ss_item_sk` = `tpcds`.`item`.`i_item_sk` WHERE `tpcds`.`item`.`i_color` = _spm_const_range(1, 10, 50) GROUP BY `tpcds`.`item`.`i_item_id`
planSQL | SELECT i_item_id, sum(ss_ext_sales_price) AS c_46 FROM (SELECT i_item_id, ss_ext_sales_price FROM store_sales INNER JOIN[SHUFFLE] (SELECT i_item_sk, i_item_id FROM item WHERE i_color = _spm_const_range(1, '10', '50')) t_0 ON ss_item_sk = i_item_sk) t_1 GROUP BY i_item_id
costs | 1.612502146E8
queryMs | -1.0
source | USER
updateTime | 2025-05-16 16:02:46
***************************[ 2. row ]***************************
Id | 647171
global | N
enable | Y
bindSQLDigest | SELECT `tpcds`.`item`.`i_item_id`, sum(`tpcds`.`store_sales`.`ss_ext_sales_price`) AS `total_sales` FROM `tpcds`.`store_sales` INNER JOIN `tpcds`.`item` ON `tpcds`.`store_sales`.`ss_item_sk` = `tpcds`.`item`.`i_item_sk` WHERE `tpcds`.`item`.`i_color` = ? GROUP BY `tpcds`.`item`.`i_item_id`
bindSQLHash | 68196091
bindSQL | SELECT `tpcds`.`item`.`i_item_id`, sum(`tpcds`.`store_sales`.`ss_ext_sales_price`) AS `total_sales` FROM `tpcds`.`store_sales` INNER JOIN `tpcds`.`item` ON `tpcds`.`store_sales`.`ss_item_sk` = `tpcds`.`item`.`i_item_sk` WHERE `tpcds`.`item`.`i_color` = _spm_const_enum(1, 60, 70, 80) GROUP BY `tpcds`.`item`.`i_item_id`
planSQL | SELECT i_item_id, sum(ss_ext_sales_price) AS c_46 FROM (SELECT i_item_id, ss_ext_sales_price FROM store_sales INNER JOIN[BROADCAST] (SELECT i_item_sk, i_item_id FROM item WHERE i_color = _spm_const_enum(1, '60', '70', '80')) t_0 ON ss_item_sk = i_item_sk) t_1 GROUP BY i_item_id
costs | 1.457490986E8
queryMs | -1.0
source | USER
updateTime | 2025-05-16 16:03:23
2 rows in set
Time: 0.011s
MySQL tpcds>
MySQL tpcds> explain select i_item_id, sum(ss_ext_sales_price) total_sales
from store_sales join item on ss_item_sk = i_item_sk
where i_color = 40 -- hit SHUFFLE JOIN
group by i_item_id
+-------------------------------------------------------------------------------------------+
| Explain String |
+-------------------------------------------------------------------------------------------+
| Using baseline plan[647167] |
| |
| PLAN FRAGMENT 0 |
.................
| | |
| 5:HASH JOIN |
| | join op: INNER JOIN (PARTITIONED) |
| | colocate: false, reason: |
| | equal join conjunct: 2: ss_item_sk = 24: i_item_sk |
| | |
| |----4:EXCHANGE |
| | |
| 1:EXCHANGE |
.................
MySQL tpcds> explain select i_item_id, sum(ss_ext_sales_price) total_sales
from store_sales join item on ss_item_sk = i_item_sk
where i_color = 70 -- hit BROADCAST JOIN
group by i_item_id
+-------------------------------------------------------------------------------------------+
| Explain String |
+-------------------------------------------------------------------------------------------+
| Using baseline plan[647171] |
| |
| PLAN FRAGMENT 0 |
.................
| 4:HASH JOIN |
| | join op: INNER JOIN (BROADCAST) |
| | colocate: false, reason: |
| | equal join conjunct: 2: ss_item_sk = 24: i_item_sk |
| | |
| |----3:EXCHANGE |
| | |
| 0:OlapScanNode |
| TABLE: store_sales |
.................
MySQL tpcds> explain select i_item_id, sum(ss_ext_sales_price) total_sales
from store_sales join item on ss_item_sk = i_item_sk
where i_color = 100 -- don't use any Baseline
group by i_item_id
+-------------------------------------------------------------------------------------------+
| Explain String |
+-------------------------------------------------------------------------------------------+
| PLAN FRAGMENT 0 |
| OUTPUT EXPRS:25: i_item_id | 46: sum |
| PARTITION: UNPARTITIONED |
| |
| RESULT SINK |
.................

Auto-Capture

Auto-Capture запрашивает SQL запросы за прошедший период времени (по умолчанию 3 часа), генерирует и сохраняет baseline на основе этих запросов, и сгенерированный baseline находится в состоянии 'disable' по умолчанию и не вступает в силу немедленно. В следующих сценариях:

  • После обновления план выполнения изменяется, что приводит к увеличению времени запроса
  • После изменения данных и изменения статистики, что приводит к изменению плана выполнения

Вы можете найти исторический baseline с помощью show baseline и вручную откатить план с помощью enable baseline.

Функция Auto-Capture зависит от функции сохранения истории запросов и требует следующих настроек:

set global enable_query_history=true;

История запросов хранится в таблице 'statistics.query_history'.

Чтобы включить автоматический захват:

set global enable_plan_capture=true;

Другие конфигурации:

-- Историческая длительность хранения истории запросов, единица: секунды, по умолчанию 3 дня
set global query_history_keep_seconds = 259200;
-- Рабочий интервал Auto-Capture, единица: секунды, по умолчанию 3 часа
set global plan_capture_interval=10800;
-- Захватывает регулярные проверки таблиц SQL, захватывает SQL только когда имена таблиц (db.table) могут соответствовать plan_capture_include_pattern, по умолчанию .*, что представляет все таблицы
set global plan_capture_include_pattern=".*";
примечание
  1. Сохранение истории запросов и Auto-Capture потребуют некоторых ресурсов хранения и вычислений, поэтому установите это разумно в соответствии с вашими собственными сценариями.
  2. После привязки baselines вновь добавленные оптимизации после обновлений могут стать неэффективными, поэтому автоматически захваченные baselines отключены по умолчанию.

Пример использования

Используйте функцию Auto-Capture во время обновлений, чтобы избежать проблем с регрессией плана после обновления:

  1. Включите функциональность Auto-Capture за 1-2 дня до обновления:
set global enable_query_history=true;
set global enable_plan_capture=true;
  1. Selena начинает периодически записывать планы запросов, которые можно просмотреть с помощью show baseline

  2. Обновите Selena

  3. После обновления проверьте время выполнения запроса или используйте следующий SQL для идентификации запросов с изменениями плана:

WITH recent_queries AS (
-- Используйте время выполнения запроса в течение 3 дней как среднее время выполнения
SELECT
dt, -- Время выполнения запроса
sql_digest, -- SQL fingerprint запроса
`sql`, -- SQL запроса
query_ms, -- Время выполнения
plan, -- Используемый план запроса
AVG(query_ms) OVER (PARTITION BY sql_digest) AS avg_ms, -- Среднее время выполнения в группе SQL fingerprint
RANK() OVER (PARTITION BY sql_digest ORDER BY plan) != 1 AS is_changed -- Подсчитайте различные форматы плана как индикатор изменения
FROM _statistics_.query_history
WHERE dt >= NOW() - INTERVAL 3 DAY
)
-- Запросы со временем выполнения выше 1.5 раз среднего за последние 12 часов
SELECT *, RANK() OVER (PARTITION BY sql_digest ORDER BY query_ms DESC) AS rnk
FROM recent_queries
WHERE query_ms > avg_ms * 1.5 and dt >= now() - INTERVAL 12 HOUR
  1. На основе информации об изменении плана или информации о времени выполнения запроса определите, необходим ли откат плана

  2. Найдите соответствующий baseline для SQL и временной точки:

show baseline on <query>
  1. Используйте Enable baseline для отката:
enable baseline <id>
  1. Включите переключатель переписывания baseline:
set enable_spm_rewrite = true;

Будущие планы

В будущем Selena предоставит более продвинутые функции на основе SQL Plan Manager, включая:

  • Улучшенные проверки стабильности для SQL планов.
  • Автоматическую оптимизацию фиксированных планов запросов.
  • Поддержку большего количества методов привязки условных параметров.