Почему мой SQL-запрос не использует составной индекс таблицы? - PullRequest
1 голос
/ 24 октября 2019

У меня есть пользователи таблица со столбцами: id (первичный ключ), type, external_id, external_type, created_at, updated_at

Индексы:

  • Первичные (id)
  • Уникальные (external_id, external_type, type)
  • Неуникальные (updated_at)

И настройки таблица с колонками: id, user_id, name, value, created_at, updated_at, type

Индексы:

  • Первичный (id)
  • Уникальный (user_id, name)
  • Неуникальный (user_id)
  • Неуникальный (updated_at)

Я выполняю запрос:

SELECT users.id, users.type, users.external_id, users.created_at, users.updated_at,

  settings.id, settings.settings_id, settings.name, settings.value, 
  settings.created_at, settings.updated_at, settings.type

FROM users

  LEFT OUTER JOIN settings on settings.user_id = users.id

WHERE users.external_id=3 and users.external_type=“Owner”

В отчете объяснения я вижу, что:

  • Для таблицы пользователей был определен индекс (external_id, external_type, type)в качестве возможного ключа, но НЕ используется
  • В таблице настроек используется индекс (user_id, name)

Цель

  • Я хочу оптимизировать этот запрос
  • Поэтому я хочу, чтобы таблица пользователей использовала составной индекс (external_id, external_type, type)

ВещиЯ сделал для отладки:

  • Если я изменю первую строку инструкции SELECT для удаления users.created_at, users.updated_at, он использует индекс
  • Если я пытаюсь добавитьнеуникальный индекс (external_id, external_type) для таблицы пользователей, он все еще не использует его
  • Если я изменю предложение WHERE для добавления запроса и users.type = «Blah», он использует индекс

Чего мне не хватает?

Ответы [ 3 ]

1 голос
/ 27 октября 2019

Это позволяет избежать двойного поиска

Ваш индекс (external_id, external_type, type), но для получения всей информации, необходимой для запроса, ему нужно будет использовать этот индекс для поиска строк, а затем использовать id, который автоматически включается в конце этого индекса для поиска столбцов created_at и updated_at в основной таблице.

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

Вы можете увидеть подтверждение этого факта с помощью вашего утверждения:

Если я изменю первую строку оператора SELECT наудалить users.created_at, users.updated_at, он использует индекс

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

Что касается следующего:

Если я изменю предложение WHERE для добавления запроса и users.type= «Бла», он использует индекс

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

Решение

Чтобы заставить его использовать индекс, вам просто нужно сделать так, чтобы он нене нужно выполнять двойной поиск, делая его индексом покрытия.

(external_id,  external_type, type, created_at, updated_at)

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

0 голосов
/ 24 октября 2019

Не уверен, какую версию mysql вы используете. До 8.0 mysql innodb не сохранял статистику, и статистика в памяти вряд ли может представлять данные, если ваши данные искажены. В вашем случае оптимизатор запросов может подумать, что сканирование таблицы будет самым быстрым, если статистика предполагает, что большинство данных в таблице пользователей с external_id = 3 и external_type = 'Owner', потому что ни один индекс в таблице не охватывает выбранные столбцы, иМеханизм запросов должен выполнять поиск данных на основе индекса, если индекс используется.

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

0 голосов
/ 24 октября 2019

Это отвечает первоначальной версии вопроса.

Возможно, вы путаете оптимизатор, используя LEFT JOIN, а затем выполняете фильтрацию в предложении WHERE.

Начните с написанияquery as:

SELECT u.id, u.type, u.external_id, u.created_at, u.updated_at,
       s.id, s.settings_id, s.name, s.value, 
       s.created_at, s.updated_at, s.type
FROM users u JOIN
     settings s
     ON s.user_id = u.id
WHERE s.external_id = 3 and s.external_type = 'Owner'

Псевдонимы таблиц просто упрощают написание и чтение запроса и не влияют на производительность.

Затем вам нужны следующие индексы:

  • settings(external_id, external_type, user_id)
  • user(id)

MySQL должен использовать индекс settings для поиска пользователей, которые соответствуют external_id и external_type, просто просматриваяих в индексе. Затем он будет использовать user_id для поиска соответствующей информации в таблице users. Это должен быть самый быстрый подход.

На самом деле, вы получаете второе бесплатно, потому что это первичный ключ. Я не беспокоюсь о создании покрывающих индексов, потому что вы выбираете так много столбцов. Но это может обеспечить чуть лучшую производительность.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...