В запросе выбираются только поля левой таблицы, а правая присоединена через LEFT JOIN. Когда оптимизатор вправе полностью устранить это соединение без изменения результата?
Оптимизатор может устранить такой LEFT JOIN, если доказано, что он не добавляет и не удаляет строки: для каждой строки левой таблицы существует не более одной совпадающей строки справа, а поля и условия, зависящие от правой таблицы, не используются в результате. Обычно это следует из ограничения UNIQUE или PRIMARY KEY на ключе соединения.
Если справа могут совпасть несколько строк, соединение удалять нельзя: оно размножает строки левой таблицы. Если запрос дополнительно фильтрует, группирует или сортирует по правой таблице, устранение также может изменить семантику.
Оптимизаторы SQL применяют эквивалентные преобразования, чтобы исполнять запрос дешевле, чем он буквально записан. Одно из таких преобразований — устранение избыточного соединения: разработчик явно описывает связи предметной области, но конкретному результату часть этих связей не нужна.
Такой подход решает проблему лишних чтений, проверок условий соединения и промежуточных строк. Он особенно полезен в запросах, построенных поверх представлений, ORM или универсальных шаблонов, где соединение может присутствовать независимо от выбранных колонок.
Рассмотрим запрос, который возвращает только идентификаторы клиентов. Если у одного клиента может быть несколько профилей, обычный LEFT JOIN вернёт идентификатор клиента несколько раз. Простое удаление соединения в таком случае изменит количество строк.
Даже отсутствие колонок правой таблицы в SELECT не доказывает избыточность соединения. Нужно анализировать его влияние на кардинальность, наличие строк и все остальные части запроса — WHERE, GROUP BY, HAVING, DISTINCT и оконные вычисления.
Для безопасного устранения LEFT JOIN оптимизатор должен установить несколько фактов:
При LEFT JOIN отсутствие совпадения справа не удаляет строку слева, поэтому отсутствие строк в правой таблице само по себе не препятствует устранению. Критичен риск размножения: если ключ справа не уникален, одна строка слева может превратиться в несколько.
В этом примере p.customer_id уникален, поля p не используются, а LEFT JOIN не удаляет клиентов без профиля. Поэтому результат эквивалентен выборке только из customers, и оптимизатор может не читать customer_profiles.
Ограничение уникальности должно быть действительно доступно оптимизатору: он использует сведения о ключах, индексах и ограничениях, а не предполагает уникальность по фактическим данным. Если уникальность не гарантирована схемой, совпадение может быть множественным даже при текущем содержимом таблицы.
DISTINCT или агрегация иногда скрывают размножение строк, но это не означает, что соединение всегда можно удалить. Оптимизатор может доказать эквивалентность только с учётом всей операции; нельзя переносить такой вывод на произвольный запрос.
Для INNER JOIN ситуация строже: даже уникальное соединение может удалить строки левой таблицы при отсутствии совпадения. Для его устранения обычно требуется дополнительное доказательство существования совпадающей строки, например гарантированный внешний ключ с подходящими ограничениями.
В ORM-запросе к списку клиентов автоматически добавлялось соединение с таблицей профилей. В конкретном отчёте выбирались только поля клиентов, но customer_profiles.customer_id не был уникальным из-за исторически накопившихся дублей.
Первый вариант — удалить соединение вручную. Он ускорял запрос, но менял число строк и ломал постраничную выдачу. Второй вариант — оставить соединение и добавить DISTINCT; результат становился корректнее для отчёта, но появлялись дополнительные сортировки или хеширование, а исходная проблема качества данных сохранялась.
Выбрали третий вариант: устранить дубли на уровне модели данных и добавить уникальное ограничение, после чего оставить запрос прозрачным для ORM. Это позволило оптимизатору безопасно устранить ненужное соединение, сохранить семантику результата и снизить стоимость чтения.
Нет. Обычный индекс ускоряет поиск, но не гарантирует, что найдено не более одной строки. Для доказательства кардинальности нужно ограничение уникальности, первичный ключ или иное проверяемое свойство схемы. Уникальный индекс может использоваться как такое доказательство, если СУБД учитывает его семантику.
Не без дополнительного анализа. Условие по правой таблице в WHERE обычно превращает поведение в фильтрацию строк, потому что для отсутствующей правой строки значения становятся NULL. Удаление соединения тогда может вернуть строки, которые исходный запрос отбрасывал.
Внешний ключ может гарантировать наличие значения, ссылающегося на родительскую таблицу, но не обязательно гарантирует, что соединение сохранит строку в конкретном запросе. Нужно учитывать NULL в ссылочном столбце, отключённые или невалидированные ограничения, дополнительные условия соединения и свойства конкретной СУБД. Кроме того, если правая таблица нужна только для проверки существования, оптимизатор может преобразовать соединение в полусоединение, но это отдельное преобразование, а не безусловное удаление.