Spec-Zone.ru › MySQL 8.4

10.2.2.1 Оптимизация предикатов подзапросов IN и EXISTS с помощью преобразований семисоединения и антисоединения

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

При внутреннем соединении двух таблиц соединение возвращает строку из одной таблицы столько раз, сколько есть совпадений в другой таблице. Но для некоторых запросов важна только информация о наличии совпадения, а не о количестве совпадений. Предположим, что есть таблицы с названиями class и roster, которые содержат список курсов по учебному плану и списки студентов (студенты, записавшиеся на каждый курс), соответственно. Чтобы отобразить курсы, на которые действительно записаны студенты, можно использовать следующее соединение:

SELECT class.class_num, class.class_name
    FROM class
    INNER JOIN roster
    WHERE class.class_num = roster.class_num;

Однако результат выводит каждый курс один раз для каждого записанного студента. Для заданного вопроса эта избыточная информация не нужна.

Предполагая, что class_num является первичным ключом в таблице class, подавление дубликатов возможно с помощью SELECT DISTINCT, но неэффективно сначала сгенерировать все соответствующие строки, а затем удалить дубликаты.

Тот же результат без дубликатов можно получить, используя подзапрос:

SELECT class_num, class_name
    FROM class
    WHERE class_num IN
        (SELECT class_num FROM roster);

Здесь оптимизатор может распознать, что условие IN требует, чтобы подзапрос возвращал только один экземпляр каждого номера курса из таблицы roster. В этом случае запрос может использовать семисоединение, то есть операцию, возвращающую только один экземпляр каждой строки в class, которая совпадает со строками в roster.

Следующее утверждение, содержащее предикат подзапроса EXISTS, эквивалентно предыдущему утверждению, содержащему предикат подзапроса IN:

SELECT class_num, class_name
    FROM class
    WHERE EXISTS
        (SELECT * FROM roster WHERE class.class_num = roster.class_num);

Любое утверждение с предикатом подзапроса EXISTS преобразуется теми же преобразованиями семисоединения, что и утверждение с эквивалентным предикатом подзапроса IN.

Следующие подзапросы преобразуются в антисоединения:

  • NOT IN (SELECT ... FROM ...)

  • NOT EXISTS (SELECT ... FROM ...).

  • IN (SELECT ... FROM ...) IS NOT TRUE

  • EXISTS (SELECT ... FROM ...) IS NOT TRUE.

  • IN (SELECT ... FROM ...) IS FALSE

  • EXISTS (SELECT ... FROM ...) IS FALSE.

Короче говоря, любое отрицание подзапроса вида IN (SELECT ... FROM ...) или EXISTS (SELECT ... FROM ...) преобразуется в антисоединение.

Антисоединение — это операция, возвращающая только строки, для которых нет совпадений. Рассмотрим показанный здесь запрос:

SELECT class_num, class_name
    FROM class
    WHERE class_num NOT IN
        (SELECT class_num FROM roster);

Этот запрос переписывается внутри как антисоединение SELECT class_num, class_name FROM class ANTIJOIN roster ON class_num, которое возвращает один экземпляр каждой строки в class, который не совпадает ни с одной строкой в roster. Это означает, что для каждой строки в class, как только будет найдено совпадение в roster, строка в class может быть отброшена.

Преобразования антисоединения в большинстве случаев не могут быть применены, если сравниваемые выражения могут быть NULL. Исключением из этого правила является то, что (... NOT IN (SELECT ...)) IS NOT FALSE и его эквивалент (... IN (SELECT ...)) IS NOT TRUE могут быть преобразованы в антисоединения.

Внешняя и внутренняя синтаксис соединения разрешены в спецификации внешнего запроса, а ссылки на таблицы могут быть базовыми таблицами, производными таблицами, ссылками на представления или общими таблицами выражений.

В MySQL подзапрос должен удовлетворять этим критериям, чтобы он обрабатывался как семисоединение (или как антисоединение, если NOT изменяет подзапрос):

  • Он должен быть частью предиката IN, = ANY или EXISTS, который появляется на верхнем уровне в предложении WHERE или ON, возможно, как член выражения AND. Например:

    SELECT ...
        FROM ot1, ...
        WHERE (oe1, ...) IN
            (SELECT ie1, ... FROM it1, ... WHERE ...);
    

    Здесь ot_i и it_i представляют таблицы во внешней и внутренней частях запроса, а oe_i и ie_i представляют выражения, которые ссылаются на столбцы во внешних и внутренних таблицах.

    Подзапрос также может быть аргументом выражения, измененного NOT, IS [NOT] TRUE или IS [NOT] FALSE.

  • Он должен быть единственным запросом SELECT без конструкций UNION.

  • Он не должен содержать предложений HAVING.

  • Он не должен содержать агрегатных функций (явно или неявно сгруппированных).

  • Он не должен иметь предложения LIMIT.

  • Внешний запрос не должен использовать тип соединения STRAIGHT_JOIN.

  • Модификатор STRAIGHT_JOIN отсутствует.

  • Количество внешних и внутренних таблиц вместе должно быть меньше максимального количества таблиц, разрешенных в соединении.

  • Подзапрос может быть коррелированным или некоррелированным. Декорреляция рассматривает тривиально коррелированные предикаты в предложении WHERE подзапроса, используемого в качестве аргумента EXISTS, и делает возможным оптимизировать его так, как если бы он использовался внутри IN (SELECT b FROM ...). Термин тривиально коррелированный означает, что предикат является предикатом равенства, что он является единственным предикатом в предложении WHERE (или комбинируется с AND), и что один операнд принадлежит таблице, указанной в подзапросе, а другой операнд принадлежит внешнему блоку запроса.

  • Ключевое слово DISTINCT разрешено, но игнорируется. Стратегии семисоединения автоматически обрабатывают удаление дубликатов.

  • Предложение GROUP BY разрешено, но игнорируется, если подзапрос также содержит одну или несколько агрегатных функций.

  • Предложение ORDER BY разрешено, но игнорируется, так как порядок не имеет значения для оценки стратегий семисоединения.

Если подзапрос соответствует приведенным выше критериям, MySQL преобразует его в семисоединение (или в антисоединение, если применимо) и делает основанный на стоимости выбор из этих стратегий:

  • Преобразуйте подзапрос в соединение или используйте извлечение таблицы и выполните запрос как внутреннее соединение между таблицами подзапроса и внешними таблицами. Извлечение таблицы извлекает таблицу из подзапроса во внешний запрос.

  • Удаление дубликатов: Выполните семисоединение так, как если бы это было соединение, и удалите дублирующие записи с помощью временной таблицы.

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

  • Неполное сканирование: Сканирование таблицы подзапроса с помощью индекса, который позволяет выбрать одно значение из каждой группы значений подзапроса.

  • Материализовать подзапрос в индексированную временную таблицу, используемую для выполнения соединения, где индекс используется для удаления дубликатов. Индекс также может быть использован позже для поиска при соединении временной таблицы с внешними таблицами; в противном случае таблица сканируется. Дополнительную информацию о материализации см. в разделе 10.2.2.2 «Оптимизация подзапросов с помощью материализации».

Каждая из этих стратегий может быть включена или отключена с помощью следующих флагов системной переменной optimizer_switch:

  • Флаг semijoin управляет использованием семисоединений и антисоединений.

  • Если semijoin включен, флаги firstmatch, loosescan, duplicateweedout и materialization позволяют более точно управлять допустимыми стратегиями семисоединения.

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

  • Если duplicateweedout отключен, в некоторых случаях оптимизатор может сгенерировать план запроса, далекий от оптимального. Это происходит из-за эвристической обрезки во время жадного поиска, чего можно избежать, установив optimizer_prune_level=0.

Эти флаги включены по умолчанию. См. раздел 10.9.2 «Переключаемые оптимизации».

Оптимизатор минимизирует различия в обработке представлений и производных таблиц. Это влияет на запросы, использующие модификатор STRAIGHT_JOIN и представление с подзапросом IN, который можно преобразовать в семисоединение. Следующий запрос иллюстрирует это, потому что изменение обработки вызывает изменение преобразования и, следовательно, другую стратегию выполнения:

CREATE VIEW v AS
SELECT *
FROM t1
WHERE a IN (SELECT b
           FROM t2);

SELECT STRAIGHT_JOIN *
FROM t3 JOIN v ON t3.x = v.a;

Оптимизатор сначала рассматривает представление и преобразует подзапрос IN в полусоединение, затем проверяет, можно ли объединить представление с внешним запросом. Поскольку модификатор STRAIGHT_JOIN во внешнем запросе предотвращает полусоединение, оптимизатор отказывается от объединения, вызывая оценку производной таблицы с использованием материализованной таблицы.

Вывод EXPLAIN указывает на использование стратегий полусоединения следующим образом:

  • Для расширенного вывода EXPLAIN текст, отображаемый последующим SHOW WARNINGS, показывает переписанный запрос, который отображает структуру полусоединения. (См. Раздел 10.8.3, «Формат расширенного вывода EXPLAIN».) Из этого вы можете понять, какие таблицы были извлечены из полусоединения. Если подзапрос был преобразован в полусоединение, вы должны увидеть, что предикат подзапроса исчез, а его таблицы и WHERE были объединены в список объединений внешнего запроса и WHERE.

  • Использование временной таблицы для устранения дубликатов показано значениями Start temporary и End temporary в столбце Extra. Таблицы, которые не были извлечены и находятся в диапазоне строк вывода EXPLAIN, покрытых Start temporary и End temporary, имеют свои rowid во временной таблице.

  • FirstMatch(tbl_name) в столбце Extra указывает на обход соединений.

  • LooseScan(m..n) в столбце Extra указывает на использование стратегии LooseScan. m и n — ключевые номера деталей.

  • Использование временной таблицы для материализации показано строками со значением select_type равным MATERIALIZED и строками со значением table равным <subqueryN>.

Преобразование полусоединения также может быть применено к оператору UPDATE или DELETE с одной таблицей, который использует предикат подзапроса [NOT] IN или [NOT] EXISTS, при условии, что оператор не использует ORDER BY или LIMIT, и что преобразования полусоединения разрешены подсказкой оптимизатора или настройкой optimizer_switch.

© 2025 Oracle
Licensed under the GPLv2 License.
https://docs.oracle.com/cd/E17952_01/mysql-8.4-en/semijoins-antijoins.html

Spec-Zone.ru

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