优化慢速排名查询

时间:2010-05-07 13:20:15

标签: mysql optimization subquery ranking

我需要优化查询以获取永久性的排名(查询本身可行,但我知道它很糟糕,我只是尝试了大量记录并且它会超时)。

我将简要解释一下这个模型。我有3个桌子:球员,球队和球员队员。我有球员,可以属于球队。听起来很明显,玩家存储在玩家桌面和团队中。在我的应用程序中,每个玩家可以随时切换团队,并且必须保留日志。但是,在给定时间,玩家被认为仅属于一个团队。现在的球员队伍是他加入的最后一支球队。

我认为球员和球队的结构不相关。我每个都有一个id列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 个答案:

答案 0 :(得分:2)

它正在杀死它的子查询 - 如果你在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)或通过将历史数据移动到完全不同的表(根据您的情况,这可能是过度杀伤),能够看到哪一行是最新的更好

答案 1 :(得分:2)

试试这个:

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)上创建一个索引(按此顺序),以便快速工作。

答案 2 :(得分:1)

我有时会发现MySQL中更复杂的查询需要分成两部分。

第一部分将所需数据拉入临时表,第二部分是试图操纵创建的数据集的查询。这样做肯定会带来显着的性能提升。

答案 3 :(得分:0)

这将使当前团队的颜色按大小排序:

  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表包含“所有者”列,则可以将上述查询加入到所有者的查询中。类似的东西:

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;

答案 4 :(得分:0)

你可以在播放器表中添加一个“last_playteam_id”列,并在每次玩家使用来自player_team表的pk更改团队时更新它。

然后你可以这样做:

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行更新为current = 0.

您还可以添加一个列“last_team_id”并保留其当前团队,您可以获得上述查询的最快结果,但对其他查询可能没什么帮助。