Лучший способ построить этот оператор MySQL с подвыборками - PullRequest
1 голос
/ 23 марта 2010

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

После публикации здесь и в других местах я начал использовать подвыборы, чтобы получить счет, но этот запрос занимает 10 или более секунд!

SELECT `items_2`.*, 
   (SELECT COUNT(*) 
   FROM `comments` 
   WHERE (comments.Script = items_2.Id) 
   AND (comments.Active = 1)) 
  AS `Comments`, 
   (SELECT COUNT(votes.Member) 
   FROM `votes` 
   WHERE (votes.Script = items_2.Id) 
   AND (votes.Active = 1)) 
  AS `votes`, 
  `countrys`.`Name` AS `Country` 
FROM `items` AS `items_2` 
INNER JOIN `members` ON items_2.Member=members.Id AND members.Active = 1 
INNER JOIN `members` AS `members_2` ON items_2.Member=members.Id 
LEFT JOIN `countrys` ON countrys.Id = members.Country 
GROUP BY `items_2`.`Id` 
ORDER BY `Created` DESC 
LIMIT 10

Мой вопрос заключается в том, является ли это правильнымсделайте это, если есть лучший способ написать это утверждение, ИЛИ если есть совершенно другой подход, который будет лучше.Должен ли я запускать подвыборки отдельно и собирать информацию?

1 Ответ

2 голосов
/ 23 марта 2010

Да, вы можете переписать подзапросы как совокупные объединения (см. Ниже), но я почти уверен, что медлительность вызвана отсутствующими индексами , а не самим запросом.Используйте EXPLAIN, чтобы увидеть, какие индексы можно добавить, чтобы ваш запрос выполнялся за доли секунды.

Для записи приведен эквивалент совокупного соединения.

SELECT `items_2`.*,
  c.cnt AS `Comments`,
  v.cnt AS `votes`,
  `countrys`.`Name` AS `Country` 
FROM `items` AS `items_2` 
INNER JOIN `members` ON items_2.Member=members.Id AND members.Active = 1 
INNER JOIN `members` AS `members_2` ON items_2.Member=members.Id 
LEFT JOIN (
  SELECT Script, COUNT(*) AS cnt 
   FROM `comments` 
   WHERE Active = 1
   GROUP BY Script
) AS c
ON c.Script = items_2.Id 
LEFT JOIN ( 
  SELECT votes.Script, COUNT(*) AS cnt 
   FROM `votes` 
   WHERE Active = 1
   GROUP BY Script
) AS v
ON v.Script = items_2.Id 
LEFT JOIN `countrys` ON countrys.Id = members.Country 
GROUP BY `items_2`.`Id` 
ORDER BY `Created` DESC 
LIMIT 10

Однако , поскольку вы используете LIMIT 10, вы почти наверняка так же хорошо (или лучше) с подзапросами, которые у вас есть в настоящее время, чем с эквивалентом агрегированного соединения, который я предоставил выше дляссылка.

Это связано с тем, что плохой оптимизатор (и MySQL далеко не звездный) может в случае запроса агрегированного соединения выполнить агрегирование COUNT(*) для полного содержимого Comments и Votes, прежде чем тратить впустую все, кроме 10 значений (ваш LIMIT), тогда как в случае вашего исходного запроса он с самого начала будет смотреть только на строгий минимум до Comments иЭто касается Votes таблиц.

Точнее, использование подзапросов так, как это делает исходный запрос, обычно приводит к тому, что называется вложеннымPS с поиском индекса.Использование агрегатных объединений обычно приводит к слиянию или хеш-объединениям со сканированием индекса или просмотром таблицы.Первые (вложенные циклы) более эффективны, чем вторые (объединение слиянием и объединение хэшей), когда число циклов невелико (в вашем случае - 10). Однако вторые становятся более эффективными, когда первое приводит к слишком большому числу циклов (десятки / сотни тысяч и более), особенно в системах с медленными дисками, но большим объемом памяти.

...