MySQL присоединяется к проблеме производительности запроса соединения - PullRequest
2 голосов
/ 11 марта 2011

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

SELECT packages.id, packages.title, subcat.id, packages.weight
FROM packages ,provider, packagestosubcat, 
     packagestocity, subcat, usertosubcat, 
     usertocity, usertoprovider 
WHERE packages.endDate >'2011-03-11 06:00:00' AND 
      usertosubcat.userid = 1 AND 
      usertocity.userid = 1 AND 
      packages.providerid = provider.id AND 
      packages.id = packagestosubcat.packageid AND 
      packages.id = packagestocity.packageid AND 
      packagestosubcat.subcatid = subcat.id AND 
      usertosubcat.subcatid = packagestosubcat.subcatid AND 
      usertocity.cityid = packagestocity.cityid AND 
      (
          provider.providertype = 'reg' OR 
          (
              usertoprovider.userid = 1 AND 
              provider.providertype != 'reg' AND 
              usertoprovider.providerid = provider.ID
          )
      ) 
GROUP BY packages.title 
ORDER BY subcat.id, packages.weight DESC

Когда я запускаю объяснение, кажется, все выглядит нормально, за исключением сканирования таблицы usertoprovider, которое, кажется, не использует ключи таблицы:

id select_type table            type    possible_keys         key       key_len ref                       rows Extra
1  SIMPLE      usertocity       ref     user,city             user      4       const                     4    Using temporary; Using filesort
1  SIMPLE      packagestocity   ref     city,packageid        city      4       usertocity.cityid         419  
1  SIMPLE      packages         eq_ref  PRIMARY,enddate       PRIMARY   4       packagestocity.packageid  1    Using where
1  SIMPLE      provider         eq_ref  PRIMARY,providertype  PRIMARY   4       packages.providerid       1    Using where
1  SIMPLE      packagestosubcat ref     subcatid,packageid    packageid 4       packages.id               1    Using where
1  SIMPLE      subcat           eq_ref  PRIMARY               PRIMARY   4       packagestosubcat.subcatid 1  
1  SIMPLE      usertosubcat     ref     userid,subcatid       subcatid  4       const                     12   Using where
1  SIMPLE      usertoprovider   ALL     userid,providerid     NULL      NULL    NULL                      3735 Using where

Как видно из приведенного выше запроса, само условие:

provider.providertype = 'reg' OR 
(
    usertoprovider.userid = 1 AND 
    provider.providertype != 'reg' AND 
    usertoprovider.providerid = provider.ID
)

Индексируются обе таблицы - провайдера и пользователя. У провайдера есть индексы для ProvridID и типа провайдера, в то время как у usertoprovider есть индексы для ИД пользователей и ИД поставщика

Количество ключей: provider.id = 47, provider.type = 1, usertoprovider.userid = 1245, usertoprovider.providerid = 6

Так что совершенно очевидно, что индексы не используются.

Далее, чтобы проверить это, я пошел дальше и:

  • Дублируется таблица usertoprovider
  • Вставить в клонированную таблицу все значения провайдеров, которые имеют providertype = 'reg'
  • Упростил условие до (usertoprovider.userid = 1 И usertoprovider.providerid = provider.ID)

Время выполнения запроса изменено с 8,1317 с. на 0,0387 с.

Тем не менее, значения провайдеров, которые имеют providertype = 'reg', действительны для всех пользователей, и я хотел бы избежать вставки этих значений в таблицу usertoprovider для всех пользователей, поскольку эти данные являются избыточными.

Может кто-нибудь объяснить, почему MySQL все еще выполняет полное сканирование и не использует ключи? Что можно сделать, чтобы этого избежать?

Ответы [ 2 ]

1 голос
/ 11 марта 2011

Похоже, что provider.providertype != 'reg' является избыточным (всегда верно), за исключением случаев, когда provider.providertype обнуляется, и вы не хотите, чтобы запрос не выполнялся по NULL.

И != не должно быть <> вместобыть стандартным SQL, хотя MySQL может разрешать !=?

По стоимости сканирования таблиц

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

Этот случай

Однако в вашем случае это также может быть связано с другой частью вашего ИЛИпункт: provider.providertype = 'reg'.Если providertype - "reg", тогда этот запрос объединяет ВСЕ строки usertoprovider (скорее всего, не того, что вы хотите), поскольку это перекрестное объединение нескольких таблиц.

Механизм базы данных правильно определил, что выСкорее всего, в любом случае вам понадобятся все строки таблицы в usertoprovider (если только тип провайдера не является "reg", но механизм также может знать!).

Запрос скрывает этот факт, так как вы группируете по (MASSIVE!) набор результатов позже и просто возвращается идентификатор пакета, так что вы не увидите, сколько строк usertoprovider было возвращено.Но это будет работать очень медленно.Избавьтесь от предложения GROUP BY, чтобы узнать, сколько строк вы фактически заставляете работать с ядром базы данных !!!

Причина, по которой вы видите значительное улучшение скорости при заполнении таблицы usertoprovider, заключается в том, что тогдакаждая строка участвует в объединении, и в случае «reg» полное перекрестное объединение не происходит.Раньше, если у вас есть 1000 строк в usertoprovider, каждая строка с type = "reg" расширяет набор результатов в 1000 раз.Теперь эта строка соединяется только с одной строкой в ​​usertoprovider, и набор результатов не раскрывается.

Если вы действительно хотите передать что-либо с providertype = 'reg', но не в своем отображении "многие ко многим"таблицу, то самым простым способом может быть использование подзапроса:

  1. Удалите usertoprovider из вашего предложения FROM
  2. Выполните следующие действия:

provider.providertype='reg' OR EXISTS (SELECT * FROM usertoprovider WHERE userid=1 AND providerid = provider.ID)

Другой метод - использовать OUTER JOIN на usertoprovider - любая строка с «reg», которой нет в таблице, будет возвращаться с одной строкой NULL вместо расширениянабор результатов.

0 голосов
/ 11 марта 2011

Хм, я знаю, что MySQL делает смешные вещи с группировкой. В любой другой РСУБД ваш запрос даже не будет выполнен. Что это вообще значит,

SELECT packages.id 
[...]
GROUP BY packages.title 
ORDER BY subcat.id, packages.weight DESC

Вы хотите сгруппировать по title. Тогда в стандартном синтаксисе SQL это означает, что вы можете выбрать только title и агрегировать функции других столбцов. MySQL волшебным образом пытается выполнить (и, вероятно, догадаться) то, что вы, возможно, хотели выполнить. Так что бы вы ожидали выбрать как packages.id? Первый соответствующий идентификатор пакета для каждого title? Или последний? И что будет означать предложение ORDER BY в отношении группировки? Как вы можете упорядочить по столбцам, которые не являются частью набора результатов (потому что на самом деле только packages.title)?

Насколько я вижу, есть два решения:

  1. Вы на правильном пути с вашим запросом, затем удалите предложение ORDER BY, потому что я не думаю, что это повлияет на ваш результат, но может серьезно замедлить ваш запрос.
  2. У вас проблема с SQL, а не производительность
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...