Использование EXPLAIN в Reindexer

EXPLAIN в Reindexer используется для анализа процесса выполнения запросов и определения среди них требующих оптимизации. Применять эту функцию можно совместно с операторами SELECT, UPDATE и DELETE.

Для использования функции укажите ключевое слово EXPLAIN перед SQL-запросом или добавьте в тело запроса "explain": true при использовании DSL, и план выполнения запроса будет возвращен с результатами.

Функция EXPLAIN возвращает объект, включающий следующую информацию о процессе выполнения запроса:

Параметр Описание Тип данных Возможные значения Примечание
total_us Общее время выполнения запроса int - -
preselect_us Время на подготовку JOIN-подзапросов (по большей части состоит из времени предварительной выборки из joined-неймспейсов) int - -
prepare_us Время подготовки и оптимизации запроса int - -
indexes_us Время выборки ключей из индексов int - -
postprocess_us Время подготовки итераторов и обработки LEFT JOIN int - -
loop_us Время работы цикла пересечения выборок по отдельным условиям int - -
general_sort_us Время выполнения сортировки результатов запроса int - -
sort_index Индекс, использующийся для сортировки результатов string - -
sort_by_uncommitted_index Признак завершения фоновой оптимизации индексов bool true,
false
-
selectors Селекторы фильтров, используемые для обработки условий запроса array - -
items Количество просканированных элементов (строк неймспейса, записей) int - Поле элемента массива selectors.
Возвращается при значении поля method = scan
field Имя поля или индекс, использующийся для обработки условия запроса string - Поле элемента массива selectors.
Возвращается при значении поля method = index или при method = scan(для этого случая field = -scan)
comparators Количество компараторов, используемых для данного селектора int - Поле элемента массива selectors
Возвращается при значении поля type = OnlyComparator
matched Количество обработанных элементов удовлетворивших компаратор или соответствующих данному селектору int - Поле элемента массива selectors
cost Ожидаемые затраты (количество итераций) на использование этого селектора int - Поле элемента массива selectors
Позволяет оценить время, которое проходит, прежде чем начнётся этап вывода данных
method Метод, используемый для обработки условия string scan — Полное сканирование.
index — Выборка из индекса.
preselected_values — Для присоединяемого неймспейса был выполнен относящийся к нему подзапрос и получены значения строк, удовлетворяющие ему. После этого была снята блокировка мьютекса с присоединяемого неймспейса и, при выполнении основного запроса, была выполнена дофильтровка по условию ON только среди выбранных на предыдущем этапе строк.
preselected_rows — Для присоединяемого неймспейса был выполнен относящийся к нему подзапрос и получены id строк, удовлетворяющие ему. После, при выполнении основного запроса, дофильтровка по условию ON выполнялась только среди этих строк.
no_preselect — Предварительная выборка из присоединяемого неймспейса не производилась
Поле элемента массива selectors
type Тип итератора (способ итерирования по выборке) string SingleRange — Итерирование по индексу в прямом порядке.
RevSingleRange — Итерирование по индексу в обратном порядке.
OnlyComparator — Выборка по индексу не проводилась, а для каждого значения выполнялась проверка условия.
Forward — Выборка в прямом направлении по нескольким id сетам, полученным от индекса.
Reverse — Выборка в обратном направлении по нескольким id сетам, полученным от индекса.
SingleIdset — Выборка в прямом направлении по единственному id сету, полученному от индекса.
RevSingleIdset — Выборка в обратном направлении по единственному id сету, полученному от индекса.
SingleIdSetWithDeferedSort — id сеты были объединены в один, по которому и происходила выборка.
RevSingleIdSetWithDeferedSort — id сеты были объединены в один, по которому и происходила выборка в обратном порядке.
Unsorted — Несортированная выборка.
UnbuiltSortOrdersIndex — Итерирование по b-tree индексу (если не произведена его оптимизация)
Поле элемента массива selectors
field_type Тип поля из запроса string non-indexed,
indexed
Поле элемента массива selectors
explain_select Explain одного из выполнений вложенного запроса к присоединяемому неймспейсу object - Поля объекта аналогичны описанным выше
explain_preselect Explain для предварительной выборки для вложенного запроса к присоединяемому неймспейсу object - Возвращается при значении поля method = preselected_rows или method = preselected_values.
Поля объекта аналогичны описанным выше
on_conditions_injections Информация о подстановке условий из ON() в основной запрос object -
namespace Имя присоединяемого неймспейса string - Поле объекта on_conditions_injections
on_condition Условие присоединения. SQL-подобная строка string - Поле объекта on_conditions_injections
type Источник значений string by_value — предварительная выборка значений,
select — дополнительная выборка
Поле объекта on_conditions_injections
total_time_us Общее время, затраченное на попытку подстановки условия int - Поле объекта on_conditions_injections
success Признак успешного выполнения подстановки boolean - Поле объекта on_conditions_injections
injected_condition Условие, подставленное в основной запрос, в результате оптимизации. SQL-подобная строка string - Поле объекта on_conditions_injections
conditions Информация о процессе обработки отдельных условий JOIN array - Поле объекта on_conditions_injections.
Каждый элемент массива содержит объект explain_select (описан выше), а также несколько дополнительных полей: condition, total_time_us, success, agg_type, values_count, new_condition
condition Условие JOIN (из on_condition). SQL-подобная строка string - Поле элемента массива conditions
total_time_us Общее время, затраченное на подстановку (от начала попытки выполнения присоединения до его успешного окончания или отказа) int - Поле элемента массива conditions
success Признак успешного выполнения подстановки boolean - Поле элемента массива conditions
agg_type Тип агрегации, использованный для подстановки string min — получить минимальное значение в столбце,
max — получить минимальное значение в столбце,
distinct — получить записи с уникальными значениями
Поле элемента массива conditions
values_count Результирующий размер выборки значений по запросу, который был использован для выборки из joined-неймспейса int Поле элемента массива conditions
new_condition Подставленное в основной запрос условие. SQL-подобная строка string Поле элемента массива conditions

Примеры запросов с EXPLAIN и ответов к ним

Запрос с Explain через HTTP

curl --location --request POST 'http://172.0.0.1:9088/api/v1/db/frontapi/query' \
--header 'Content-Type: application/json' \
--data-raw '{
  "namespace": "media_items",
  "type": "select",
  "filters": [
    {
      "field": "year",
      "cond": "GE",
      "value": [
        2000
      ],
      "join_query": {
        "namespace": "favorites",
        "type": "INNER",
        "on": [
          {
            "left_field": "id",
            "right_field": "content_id",
            "cond": "EQ"
          }
        ]
      }
    }
  ],
  "explain": true
}'
{
    "items": [
        {
            "id": 1,
            "year": 2001,
            "name": "Суперфильм",
            "joined_favorites": [
                {
                    "id": 0,
                    "content_id": 1,
                    "description": "Описание суперфильма"
                }
            ]
        }
    ],
    "namespaces": [
        "media_items",
        "favorites"
    ],
    "cache_enabled": true,
    "explain": {
        "total_us": 420,
        "preselect_us": 51,
        "prepare_us": 254,
        "indexes_us": 17,
        "postprocess_us": 16,
        "loop_us": 80,
        "general_sort_us": 0,
        "sort_index": "-",
        "sort_by_uncommitted_index": false,
        "selectors": [
            {
                "keys": 1,
                "cost": 1,
                "field": "id",
                "field_type": "indexed",
                "matched": 1,
                "method": "index",
                "type": "SingleIdset"
            },
            {
                "field": "inner_join favorites",
                "matched": 1,
                "selects_count": 1,
                "join_select_total": 60,
                "method": "no_preselect",
                "keys": 0,
                "explain_select": {
                    "total_us": 42,
                    "preselect_us": 0,
                    "prepare_us": 8,
                    "indexes_us": 8,
                    "postprocess_us": 17,
                    "loop_us": 8,
                    "general_sort_us": 0,
                    "sort_index": "-",
                    "sort_by_uncommitted_index": false,
                    "selectors": [
                        {
                            "keys": 1,
                            "cost": 1,
                            "field": "content_id",
                            "field_type": "indexed",
                            "matched": 1,
                            "method": "index",
                            "type": "SingleIdset"
                        }
                    ]
                }
            }
        ],
        "on_conditions_injections": [
            {
                "namespace": "favorites",
                "on_condition": "INNER JOIN ON (favorites.content_id = id)",
                "type": "select",
                "total_time_us": 216,
                "success": true,
                "injected_condition": "(id IN '1' )",
                "conditions": [
                    {
                        "condition": "favorites.content_id = id",
                        "total_time_us": 196,
                        "success": true,
                        "explain_select": {
                            "total_us": 56,
                            "preselect_us": 0,
                            "prepare_us": 8,
                            "indexes_us": 9,
                            "postprocess_us": 23,
                            "loop_us": 14,
                            "general_sort_us": 0,
                            "sort_index": "-",
                            "sort_by_uncommitted_index": false,
                            "selectors": [
                                {
                                    "keys": 1,
                                    "cost": 1,
                                    "field": "content_id",
                                    "field_type": "indexed",
                                    "matched": 1,
                                    "method": "index",
                                    "type": "SingleIdset"
                                }
                            ]
                        },
                        "agg_type": "distinct",
                        "values_count": 1,
                        "new_condition": "id IN '1'"
                    }
                ]
            }
        ]
    }
}

SQL-запрос с Explain через графический интерфейс Reindexer Face

При отправке запроса с Explain через Reindexer Face план его выполнения отображается в виде таблицы. Если в запросе есть joined-подзапросы, планы их выполнения можно посмотреть в отдельных вкладках.

EXPLAIN SELECT * FROM media_items WHERE year > 2000 JOIN favorites ON media_items.id = favorites.content_id

Таблица с планом выполнения запроса

Таблица с планом выполнения запроса

Таблица с планом выполнения joined-подзапроса

Таблица с планом выполнения joined-подзапроса

Подробнее о выполнении запросов через графический интерфейс — в разделе «Выполнение SQL-запросов через Reindexer Face»

SQL-запрос с Explain через утилиту reindexer_tool

Подробнее об утилите — в разделе «Утилита reindexer_tool для управления БД Reindexer через командную строку».

EXPLAIN SELECT * FROM media_items WHERE year > 2000 JOIN favorites ON media_items.id = favorites.content_id
[
{"id":1,"year":2001,"name":"Суперфильм","joined_favorites":[{"id":0,"content_id":1,"description":"Описание суперфильма"}]}
]
Explain: 
{
  "total_us": 309,
  "preselect_us": 87,
  "prepare_us": 23,
  "indexes_us": 16,
  "postprocess_us": 165,
  "loop_us": 15,
  "general_sort_us": 0,
  "sort_index": "-",
  "sort_by_uncommitted_index": false,
  "selectors": [
    {
      "items": 1,
      "field": "-scan",
      "matched": 1,
      "method": "scan",
      "type": "SingleRange"
    },
    {
      "comparators": 1,
      "field": "year",
      "cost": 2,
      "method": "scan",
      "matched": 1,
      "type": "Comparator",
      "condition": "> 2000",
      "field_type": "indexed"
    },
    {
      "field": "left_join favorites",
      "matched": 1,
      "selects_count": 1,
      "join_select_total": 124,
      "method": "no_preselect",
      "keys": 0,
      "explain_select": {
        "total_us": 68,
        "preselect_us": 0,
        "prepare_us": 18,
        "indexes_us": 15,
        "postprocess_us": 22,
        "loop_us": 12,
        "general_sort_us": 0,
        "sort_index": "-",
        "sort_by_uncommitted_index": false,
        "selectors": [
          {
            "keys": 1,
            "cost": 1,
            "field": "content_id",
            "field_type": "indexed",
            "matched": 1,
            "method": "index",
            "type": "SingleIdset"
          }
        ]
      }
    }
  ]
}
Returned 1 rows

Запрос с Explain на go

type Favorite struct {
    ID        int    `reindex:"id,,pk" json:"id"`
    ContentID int    `reindex:"content_id,tree" json:"content_id"`
}

type MediaItem struct {
    ID        int         `reindex:"id,,pk" json:"id"`
    Year      int         `reindex:"year,tree" json:"year"`
    Favorites []*Favorite `reindex:"joined_favorites,,joined"`
}
...

query := db.Query("media_items").Explain().Where("year", reindexer.GE, 2000).
    InnerJoin(db.Query("favorites"), "joined_favorites").
    On("id", reindexer.SET, "content_id")

iterator := query.Exec()
defer iterator.Close()

if err := iterator.Error(); err != nil {
    fmt.Println("Ошибка при выполнении запроса:", err)
    return
}

results, err := iterator.GetExplainResults()
if err != nil {
    fmt.Println("Ошибка при получении результатов:", err)
    return
}

jsonResults, err := json.MarshalIndent(results, "", "    ")
if err != nil {
    fmt.Println("Ошибка при сериализации в JSON:", err)
    return
}

fmt.Println(string(jsonResults))
{
    "total_us": 307,
    "preselect_us": 48,
    "prepare_us": 140,
    "indexes_us": 8,
    "postprocess_us": 15,
    "loop_us": 94,
    "sort_index": "-",
    "general_sort_us": 0,
    "sort_by_uncommitted_index": false,
    "selectors": [
        {
            "field": "id",
            "field_type": "indexed",
            "method": "index",
            "keys": 1,
            "comparators": 0,
            "cost": 1,
            "matched": 1,
            "items": 0
        },
        {
            "field": "year",
            "field_type": "indexed",
            "method": "scan",
            "keys": 0,
            "comparators": 1,
            "cost": 2,
            "matched": 1,
            "items": 0
        },
        {
            "field": "inner_join favorites",
            "method": "no_preselect",
            "keys": 0,
            "comparators": 0,
            "cost": 0,
            "matched": 1,
            "items": 0,
            "explain_select": {
                "total_us": 48,
                "preselect_us": 0,
                "prepare_us": 10,
                "indexes_us": 10,
                "postprocess_us": 17,
                "loop_us": 9,
                "sort_index": "-",
                "general_sort_us": 0,
                "sort_by_uncommitted_index": false,
                "selectors": [
                    {
                        "field": "content_id",
                        "field_type": "indexed",
                        "method": "index",
                        "keys": 1,
                        "comparators": 0,
                        "cost": 1,
                        "matched": 1,
                        "items": 0
                    }
                ]
            }
        }
    ],
    "on_conditions_injections": [
        {
            "namespace": "favorites",
            "on_condition": "INNER JOIN ON (favorites.content_id IN id)",
            "total_time_us": 112,
            "success": true,
            "type": "select",
            "injected_condition": "(id IN '1' )",
            "conditions": [
                {
                    "condition": "favorites.content_id IN id",
                    "total_time_us": 91,
                    "explain_select": {
                        "total_us": 52,
                        "preselect_us": 0,
                        "prepare_us": 7,
                        "indexes_us": 14,
                        "postprocess_us": 17,
                        "loop_us": 13,
                        "sort_index": "-",
                        "general_sort_us": 0,
                        "sort_by_uncommitted_index": false,
                        "selectors": [
                            {
                                "field": "content_id",
                                "field_type": "indexed",
                                "method": "index",
                                "keys": 1,
                                "comparators": 0,
                                "cost": 1,
                                "matched": 1,
                                "items": 0
                            }
                        ]
                    },
                    "agg_type": "distinct",
                    "success": true,
                    "new_condition": "id IN '1'",
                    "values_count": 1
                }
            ]
        }
    ]
}