【问题标题】:Optimize slow ranking query优化慢排名查询
【发布时间】:2010-05-07 13:20:14
【问题描述】:

我需要优化一个查询,以获得永远需要的排名(查询本身有效,但我知道这很糟糕,我刚刚尝试了很多记录,但它给出了超时)。

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

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

【问题讨论】:

  • 您是否要永久离开 player_team 中曾经发生过的所有球员球队组合?您是否没有以任何方式对此进行标记(历史关系为 0,当前关系为 1 的列会很好)?
  • 是的,我要离开组合,因为我必须保留日志。我想过拥有一个当前的标志,如果没有更好的选择,我可能会这样做。但我想也许有更好的方法。 (我是 sql 菜鸟!)不过,谢谢你的建议。
  • 您是否使用了生成此表的 Web 框架? (顾名思义,我认为 Rails 使用相同的方案)如果是这样,您可以更改您的 Rails 模型,使其具有用于此连接的所谓直通模型并附加数据,例如成员资格是否为最新。如果没有这个,我看不出你如何跟踪没有更换球队但不是某人球队当前成员的球员。 (诚​​然,我可能不了解您的 facebook 应用程序的域)
  • @marr75。不,我只是习惯了这种命名惯例,因为我已经习惯了,但这是普通的 PHP + mysql。
  • 酷,有道理。您还可以为连接表上的连接列添加唯一约束。或者查询中某处的不同子句,你可能读得很重,所以约束会更好。

标签: mysql optimization subquery ranking


【解决方案1】:

试试这个:

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

【讨论】:

  • 谢谢夸斯诺伊。我认为您的意思是 pt.id = q.mid 在 ON 条件下;改变它并工作。我试过这个,结果很快就出来了。尚未检查结果是否正确,但会尽快检查。再次感谢!
  • 抱歉,我的意思是第二个 ON 条件,应该是“t.id= q2.team_id”而不是“t.team_id = q2.team_id”
  • 我昨天有点着急,所以我无法真正尝试你的解决方案。但今天我做到了,而且效果很好。结果是正确的,并且查询使用我的测试数据运行得很快(player_team 中有 150000 条记录,坦率地说,这对这个应用程序非常乐观)。此外,您的查询帮助我作为编写其他相关查询的模板(列出给定球队的球员详细信息,获取球员人数等)。我很高兴我不必添加(和维护)额外的“当前”标志,所以不用担心一件事。所以,再次感谢,你的回答真的很有帮助。
【解决方案2】:

它是杀死它的子查询 - 如果您在 player_team 表上添加一个 current 字段,如果它是当前的,则给它 value = 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)或将历史数据移动到一个完全不同的表中(根据您的情况,这可能是矫枉过正)来查看当前行要好得多。

【讨论】:

  • 谢谢。我正在考虑添加该列。只是想看看有没有其他选择。
  • 除了当前标志,您还可以添加另外两列,activate_datetime 和 inactivate_datetime,这样您就可以知道实际转换发生的时间。
  • @Nitin Midha。谢谢你的建议。实际上,我确实有一个“已创建”列来存储插入行的时间戳(即玩家加入团队的时间)。我只是试图将不太重要的内容从帖子中删除,以免造成太多混乱。
【解决方案3】:

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

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

【讨论】:

  • 谢谢。这是我想到的第一个想法(但有一张实际的桌子)。我正在考虑的另一个选项是有一个标志来将 player_team 关​​系标记为当前/活动。
【解决方案4】:

这将获得按大小排列颜色的当前团队:

  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;

【讨论】:

    【解决方案5】:

    您可以在 player 表中添加一列“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”列并将其保留为当前团队,您可以获得上述查询的最快结果,但它对其他查询的帮助可能较小。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-01-31
      • 2015-11-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多