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 TRUEEXISTS (SELECT ... FROM ...) IS NOT TRUE.IN (SELECT ... FROM ...) IS FALSEEXISTS (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_иiit_представляют таблицы во внешней и внутренней частях запроса, аioe_иiie_представляют выражения, которые ссылаются на столбцы во внешних и внутренних таблицах.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равным<subquery.N>
Преобразование полусоединения также может быть применено к оператору UPDATE или DELETE с одной таблицей, который использует предикат подзапроса [NOT] IN или [NOT] EXISTS, при условии, что оператор не использует ORDER BY или LIMIT, и что преобразования полусоединения разрешены подсказкой оптимизатора или настройкой optimizer_switch.
© 2025 Oracle
Licensed under the GPLv2 License.