2010-03-23 4 views
1

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

После публикации здесь и в других местах я начал использовать подзапросы, чтобы получить подсчеты, но этот запрос занимает 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 

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

ответ

2

Да, вы можете переписать подзапросы как объединения агрегатов (см. Ниже), но я почти уверен, что медленность вызвана недостающими индексами, а не самим запросом. Используйте 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), тогда как в случае вашего первоначального запроса он с самого начала будет смотреть только на строгий минимум до таблиц и Votes.

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

+0

Вы сохранили этот день. Благодаря! – Corey