Оптимизировать медленный запрос ранжирования - PullRequest
1 голос
/ 07 мая 2010

Мне нужно оптимизировать запрос для ранжирования, которое занимает вечность (сам запрос работает, но я знаю, что он ужасен, и я только что попробовал его с большим количеством записей, и он дает время ожидания).

Я кратко объясню модель. У меня есть 3 стола: игрок, команда и player_team. У меня есть игроки, которые могут принадлежать команде. Очевидно, что игроки хранятся в таблице игроков, а команды в команде. В моем приложении каждый игрок может сменить команду в любое время, и необходимо вести журнал. Тем не менее, игрок считается принадлежащим только одной команде в данный момент. Текущая команда игрока - последняя, ​​к которой он присоединился.

Структура игрока и команды не имеет отношения, я думаю. У меня есть идентификатор столбца PK в каждом. В player_team у меня есть:

id          (PK)
player_id   (FK -> player.id)
team_id     (FK -> team.id)

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

Моя первая идея состояла в том, чтобы сначала получить текущих игроков из player_team (то есть один рекорд для каждого игрока; эта запись должна быть текущей командой игрока). Мне не удалось найти простой способ сделать это (пробовал GROUP BY player_team.player_id HAVING player_team.id = MAX (player_team.id), но это не помогло.

Я пробовал несколько запросов, которые не работали, но сумели заставить это работать.

SELECT 
    COUNT(*) AS total,
    pt.team_id,
    p.facebook_uid AS owner_uid, 
    t.color 
FROM 
    player_team pt 
JOIN player p ON (p.id = pt.player_id)  
JOIN team t ON (t.id = pt.team_id) 
WHERE 
    pt.id IN (
        SELECT max(J.id) 
        FROM player_team J 
        GROUP BY J.player_id
    )  

GROUP BY 
    pt.team_id 
ORDER BY 
    total DESC 
LIMIT 50            

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

Кстати, я использую mysql.

Заранее спасибо

Добавление объяснения. (Извините, не уверен, как правильно отформатировать его)

id  select_type     table   type    possible_keys   key     key_len     ref     rows    Extra
1   PRIMARY     t   ALL     PRIMARY     NULL    NULL    NULL    5000    Using temporary; Using filesort
1   PRIMARY     pt  ref     FKplayer_pt77082,FKplayer_pt265938,new_index    FKplayer_pt77082    4   t.id    30  Using where
1   PRIMARY     p   eq_ref  PRIMARY     PRIMARY     4   pt.player_id    1
2   DEPENDENT SUBQUERY  J   index   NULL    new_index   8   NULL    150000  Using index

Ответы [ 5 ]

2 голосов
/ 07 мая 2010

Это подзапрос, который его убивает - если вы добавите поле current в таблицу player_team, где вы дадите ему значение = 1, если оно текущее, и 0, если оно старое, вы можете упростить это много просто делаю:

SELECT 
    COUNT(*) AS total,
    pt.team_id,
    p.facebook_uid AS owner_uid, 
    t.color 
FROM 
    player_team pt 
JOIN player p ON (p.id = pt.player_id)  
JOIN team t ON (t.id = pt.team_id) 
WHERE 
    player_team.current = 1 
GROUP BY 
    pt.team_id 
ORDER BY 
    total DESC 
LIMIT 50  

Наличие нескольких записей в таблице player_team для одних и тех же отношений, где единственный способ определить, какая из них является «текущей» записью, - это сравнение двух (или более) строк, которые я считаю плохой практикой. Я уже бывал в такой ситуации, и обходные пути, которые вы должны сделать, чтобы заставить его работать, действительно снижают производительность. Гораздо лучше иметь возможность увидеть, какая строка является текущей, выполнив простой поиск (в данном случае where current=1) - или переместив исторические данные в совершенно другую таблицу (в зависимости от вашей ситуации это может быть излишним).

2 голосов
/ 07 мая 2010

Попробуйте:

SELECT  t.*, cnt
FROM    (
        SELECT  team_id, COUNT(*) AS cnt
        FROM    (
                SELECT  player_id, MAX(id) AS mid
                FROM    player_team
                GROUP BY
                        player_id
                ) q
        JOIN    player_team pt
        ON      pt.id = q.mid
        GROUP BY
                team_id
        ) q2
JOIN    team t
ON      t.id = q2.team_id
ORDER BY
        cnt DESC
LIMIT 50

Создайте индекс для player_team (player_id, id) (в этом порядке), чтобы он работал быстро.

1 голос
/ 07 мая 2010

Иногда я обнаруживаю, что более сложные запросы в MySQL нужно разбить на две части.

Первый фрагмент будет перетаскивать необходимые данные во временную таблицу, а второй - запрос, который пытается манипулировать созданным набором данных. Это определенно приводит к значительному увеличению производительности.

0 голосов
/ 07 мая 2010

Вы можете добавить столбец "last_playteam_id" в таблицу игроков и обновлять ее каждый раз, когда игрок меняет свою команду с помощью pk из таблицы player_team.

Тогда вы можете сделать это:

SELECT 
    COUNT(*) AS total,
    pt.team_id,
    p.facebook_uid AS owner_uid, 
    t.color 
FROM 
    player_team pt 
JOIN player p ON (p.id = pt.player_id)  and p.last_playteam_id = pt.id
JOIN team t ON (t.id = pt.team_id) 
GROUP BY 
    pt.team_id 
ORDER BY 
    total DESC 
LIMIT 50   

Это может быть быстрее, потому что вам не нужно обновлять старые строки player_team до текущих = 0.

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

0 голосов
/ 07 мая 2010

Получит текущие команды с цветами, упорядоченными по размеру:

  SELECT team_id, COUNT(player_id) c AS total, t.color 
    FROM player_team pt JOIN teams t ON t.team_id=pt.team_id  
    GROUP BY pt.team_id WHERE current=1
    ORDER BY pt.c DESC
    LIMIT 50;

Но вы не дали условия, при котором игрок должен считаться владельцем команды. Ваш текущий запрос произвольно показывает одного игрока как owner_id из-за группировки, а не потому, что этот игрок является фактическим владельцем. Если ваша таблица player_team содержит столбец «owner», вы можете присоединить указанный выше запрос к запросу владельцев. Что-то вроде:

SELECT o.facebook_uid, a.team_id, a.color, a.c
FROM player_teams pt1 
  JOIN players o ON (pt1.player_id=o.player_id AND o.owner=1)
  JOIN (...above query...) a
    ON a.team_id=pt1.team_id;
...