Spec-Zone.ru › MariaDB

Руководство по трассировке оптимизатора




Трассировка оптимизатора использует формат JSON. Это, по сути, структурированный журнал, показывающий действия, предпринятые оптимизатором запросов.

Пример



Рассмотрим простой запрос:

MariaDB> explain select * from t1 where a<10;
+------+-------------+-------+-------+---------------+------+---------+------+------+-----------------------+
| id   | select_type | table | type  | possible_keys | key  | key_len | ref  | rows | Extra                 |
+------+-------------+-------+-------+---------------+------+---------+------+------+-----------------------+
|    1 | SIMPLE      | t1    | range | a             | a    | 5       | NULL | 10   | Using index condition |
+------+-------------+-------+-------+---------------+------+---------+------+------+-----------------------+

Полную трассировку можно посмотреть здесь. Если взять только имена компонентов, то получится:

MariaDB> select * from information_schema.optimizer_trace limit 1\G
*************************** 1. row ***************************
                            QUERY: select * from t1 where a<10
                            TRACE: 
{
  "steps": [
    {
      "join_preparation": { ... }
    },
    {
      "join_optimization": {
        "select_id": 1,
        "steps": [
          { "condition_processing": { ... } },
          { "table_dependencies": [ ... ] },
          { "ref_optimizer_key_uses": [ ... ] },
          { "rows_estimation": [
              {
                "range_analysis": {
                   "analyzing_range_alternatives" : { ... },
                  "chosen_range_access_summary": { ... },
                },
                "selectivity_for_indexes" : { ... },
                "selectivity_for_columns" : { ... }
              }
            ]
          },
          { "considered_execution_plans": [ ... ] },
          { "attaching_conditions_to_tables": { ... } }
         ]
      }
    },
    {
      "join_execution": { ... }
    }
  ]
}

Структура трассировки

Для каждого SELECT есть два «Шага»:

  • join_preparation
  • join_optimization

Подготовка объединения демонстрирует ранние переписывания запроса. join_optmization — это место, где выполняется большая часть оптимизаций запроса. Они включают:

  • condition_processing — базовые переписывания в условиях WHERE/ON.
  • ref_optimizer_key_uses — Построение возможных способов выполнения доступа ref и eq_ref.
  • rows_estimation — Учёт доступа range и index_merge.
  • considered_execution_plans — Сама оптимизация объединения, то есть выбор порядка объединения.
  • attaching_conditions_to_tables — После того, как порядок объединения установлен, части условия WHERE «присоединяются» к таблицам, чтобы отфильтровать строки как можно раньше.

Эти шаги выполняются только для одного SELECT. Если запрос содержит подзапросы, каждый SELECT будет иметь эти шаги, и будут дополнительные шаги/переписывания для обработки самого подзапроса.

Извлечение компонентов трассировки

Если вас интересует какая-то определённая часть трассировки, в MariaDB есть две полезные функции:

  • JSON_EXTRACT извлекает часть документа JSON
  • JSON_DETAILED представляет её в удобочитаемом виде.

Например, содержимое узла analyzing_range_alternatives можно извлечь следующим образом:

MariaDB> select JSON_DETAILED(JSON_EXTRACT(trace, '$**.analyzing_range_alternatives')) 
   ->   from INFORMATION_SCHEMA.OPTIMIZER_TRACE\G
*************************** 1. row ***************************
JSON_DETAILED(JSON_EXTRACT(trace, '$**.analyzing_range_alternatives')): [
    {
        "range_scan_alternatives": 
        [
            {
                "index": "a_b_c",
                "ranges": 
                [
                    "(1) <= (a,b) < (4,50)"
                ],
                "rowid_ordered": false,
                "using_mrr": false,
                "index_only": false,
                "rows": 4,
                "cost": 6.2509,
                "chosen": true
            }
        ],
        "analyzing_roworder_intersect": 
        {
            "cause": "too few roworder scans"
        },
        "analyzing_index_merge_union": []
    }
]

Примеры различной информации в трассировке

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

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

explain select * from t1 where not (col1 >= 3);

Трассировка оптимизатора покажет:

"steps": [
  {
    "join_preparation": {
      "select_id": 1,
      "steps": [
        {
          "expanded_query": "select t1.a AS a,t1.b AS b,t1.col1 AS col1 from t1 where t1.col1 < 3"
        }

Здесь видно, что NOT был удалён.

Аналогично, видно, что IN(...) с одним элементом эквивалентен равенству:

explain select * from t1 where col1  in (1);

покажет

  "join_preparation": {
    "select_id": 1,
    "steps": [
      {
        "expanded_query": "select t1.a AS a,t1.b AS b,t1.col1 AS col1 from t1 where t1.col1 = 1"

В то же время, преобразование столбца UTF-8 в UTF-8 не удаляется:

explain select * from t1 where convert(utf8_col using utf8) = 'hello';

покажет

  "join_preparation": {
    "select_id": 1,
    "steps": [
      {
        "expanded_query": "select t1.a AS a,t1.b AS b,t1.col1 AS col1,t1.utf8_col AS utf8_col from t1 where convert(t1.utf8_col using utf8) = 'hello'"
          }

поэтому избыточные CONVERT вызовы следует использовать с осторожностью.

Обработка представлений

MariaDB использует два алгоритма для обработки представлений: объединение и материализация. Если вы выполняете запрос, использующий представление, в трассировке будет либо

            "view": {
              "table": "view1",
              "select_id": 2,
              "algorithm": "merged"
            }

или

          {
            "view": {
              "table": "view2",
              "select_id": 2,
              "algorithm": "materialized"
            }
          },

в зависимости от используемого алгоритма.

Оптимизатор диапазонов — какие диапазоны будут сканированы

У MariaDB есть сложная часть, называемая оптимизатором диапазонов. Это модуль, который анализирует условия WHERE (и ON) и строит диапазоны индексов, которые необходимо сканировать для ответа на запрос. Правила построения диапазонов довольно сложны.

Пример: Рассмотрим таблицу

create table some_events ( 
  start_date date, 
  end_date date, 
  ...
  key (start_date, end_date)
);

и запрос:

explain select * from some_events where start_date >= '2019-09-10' and end_date <= '2019-09-14';
+------+-------------+-------------+------+---------------+------+---------+------+------+-------------+
| id   | select_type | table       | type | possible_keys | key  | key_len | ref  | rows | Extra       |
+------+-------------+-------------+------+---------------+------+---------+------+------+-------------+
|    1 | SIMPLE      | some_events | ALL  | start_date    | NULL | NULL    | NULL | 1000 | Using where |
+------+-------------+-------------+------+---------------+------+---------+------+------+-------------+

Можно предположить, что оптимизатор сможет использовать ограничения как на start_date, так и на end_date, чтобы построить узкий диапазон для сканирования. Но это не так, одно из ограничений создаёт диапазон с левой границей, а другое — с правой, поэтому их нельзя объединить.

select 
   JSON_DETAILED(JSON_EXTRACT(trace, '$**.analyzing_range_alternatives')) as trace 
from information_schema.optimizer_trace\G
*************************** 1. row ***************************
trace: [
    {
        "range_scan_alternatives": 
        [
            {
                "index": "start_date",
                "ranges": 
                [
                    "(2019-09-10,NULL) < (start_date,end_date)"
                ],
...

полученный диапазон использует только одну из границ.

Варианты доступа ref

Индексные вложенные циклы объединения называются «доступом ref» в MariaDB оптимизаторе.

Оптимизатор анализирует условия WHERE/ON и собирает все условия равенства, которые могут быть использованы для доступа ref с использованием индекса.

Список условий можно найти в узле ref_optimizer_key_uses. (TODO пример)

Оптимизация объединения

Узел оптимизатора объединения называется considered_execution_plans.

Оптимизатор строит порядок объединения слева направо. То есть, если запрос представляет собой объединение трёх таблиц:

select * from t1, t2, t3 where ...

то оптимизатор будет

  • Выбирать первую таблицу (скажем, это t1),
  • рассматривать добавление другой таблицы (скажем, t2) и строить префикс «t1, t2»
  • рассматривать добавление третьей таблицы (t3) и построение префикса «t1, t2, t3», который представляет собой полное объединение. Также будут рассматриваться и другие порядки объединения.

Основная операция здесь: «дан префикс объединения таблиц A,B,C ..., попробовать добавить к нему таблицу X». В JSON это выглядит так:

      {
        "plan_prefix": ["t1", "t2"],
        "table": "t3",
        "best_access_path": {
          "considered_access_paths": [
            {
              ...
            }
          ]
        }
      }

(искать plan_prefix за которым следует table).

Если вас интересует, как был построен (или не был построен) порядок объединения #t1,t2,t3#, вам нужно искать эти шаблоны:

  • "plan_prefix":[], "table":"t1"
  • "plan_prefix":["t1"], "table":"t2"
  • "plan_prefix":["t1", "t2"], "table":"t3"
Содержимое, воспроизведенное на этом сайте, является собственностью соответствующих владельцев, и это содержимое не проходит предварительной проверки MariaDB. Мнения, информация и мнения, выраженные в этом контенте, не обязательно отражают точку зрения MariaDB или любой другой стороны.

© 2023 MariaDB
Licensed under the Creative Commons Attribution 3.0 Unported License and the GNU Free Documentation License.
https://mariadb.com/kb/en/optimizer-trace-guide/

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API