2

我在 mysql 中的高分列表存在很大的性能问题。基本上我们有用户,我们有游戏,用户可以玩游戏并在这些游戏中获得分数。我们希望有一个可通过自定义过滤器浏览的顶级列表,因此用户可以显示所有分数或按游戏 X 或其他因素过滤它。不幸的是,当前的列表完全没有性能。它目前有 50 万个数据集,而我们的选择查询需要 20 多秒,这太长了。

这是当前的表结构:

CREATE TABLE IF NOT EXISTS `score` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT, # just an AI id
  `userid` int(10) unsigned NOT NULL, # foreign key for `users` table
  `gameid` varchar(255) NOT NULL, # foreign key for `games` table
  `date` date NOT NULL, # date of score
  `score` smallint(4) unsigned NOT NULL, # total score
  `score_level1` smallint(4) unsigned NOT NULL, # score in level 1
  `score_level2` smallint(4) unsigned NOT NULL, # score in level 2
  `score_level3` smallint(4) unsigned NOT NULL, # score in level 3
  `score_level4` smallint(4) unsigned NOT NULL, # score in level 4
  `score_level5` smallint(4) unsigned NOT NULL, # score in level 5
  `times_played` smallint(4) unsigned NOT NULL, # this is the n-th time the user plays this game (I want to know which score was his 1st try, which score was his 5th try etc)
  PRIMARY KEY (`id`),
  UNIQUE KEY `user_game_date` (`userid`,`gameid`,`date`), # we save only the latest score per user per game per day (this unique index is for "replace into")
  UNIQUE KEY `user_game_times` (`userid`,`gameid`,`times_played`), # obviously a user cant play the game multiple times for the 3rd time
) ENGINE=InnoDB  DEFAULT CHARSET=utf8;

ADD CONSTRAINT `score_ibfk_3` FOREIGN KEY (`userid`) REFERENCES `users` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION,
ADD CONSTRAINT `score_ibfk_4` FOREIGN KEY (`gameid`) REFERENCES `games` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION;

我们的选择查询如下所示:

SELECT `users`.`name` AS `username`, # we want to display who got a certain score
    `users`.`country` AS `country`, # we want to display country of a user. also users can filter the scorelist to display "italy only" for example
    `games`.`name` AS `gamename`, # show which game score is for
    `score`.`score` AS `score`, # the score, obviously
    `score`.`score_level1` AS `score1`, # display which score the user got in every level
    `score`.`score_level2` AS `score2`,
    `score`.`score_level3` AS `score3`,
    `score`.`score_level4` AS `score4`,
    `score`.`score_level5` AS `score5`,
    `score`.`times_played` AS `times_played`, # show how many attempts a user needed to achieve this very score
FROM `score`
INNER JOIN ( # this inner query is to make that we only display the latest score per user per game (yes, we do NOT want to display the highest score, but the latest). we need to keep the old data in the database though to display the score progress to the user on a different page
    SELECT `userid`,
        `gameid`,
        MAX(`date`) AS `date`
    FROM `score`
    GROUP BY `userid`, `gameid`
    ORDER BY `score` DESC
) `temp` ON `score`.`userid` = `temp`.`userid` AND `score`.`gameid` = `temp`.`gameid` AND `score`.`date` = `temp`.`date`
INNER JOIN `users` ON (`users`.`id` = `score`.`userid`)
INNER JOIN `games` ON (`games`.`id` = `score`.`gameid`)
$filter # php variable to filter. this can be empty or contain something like "where `gameid` = 4" or "where `users`.`country` = 'it'"
LIMIT :limit,:limit2 # this is done by paging function. we display 50 entries per page. (e.g. "0, 50" for page 1, "50, 50" for page 2 etc)

解释选择查询:

id  select_type     table       type        possible_keys               key         key_len     ref                     rows    Extra
1   PRIMARY     <derived2>  ALL         NULL                    NULL        NULL        NULL                    585106  
1   PRIMARY     users       eq_ref      PRIMARY                 PRIMARY     4       temp.userid                 1   
1   PRIMARY     games       eq_ref      PRIMARY                 PRIMARY     767         temp.gameid                 1   
1   PRIMARY     score       eq_ref      user_game_date,user_game_times,games    user_game_date  774         temp.userid,games.id,temp.date      1   Using where
2   DERIVED     score       index       NULL                    user_game_date  774         NULL                    608211  Using temporary; Using filesort

选择查询的分析(限制为“0,50”并且$filter为空):

Status              Duration
starting            0.000018
Waiting for query cache lock    0.000003
checking query cache for query  0.000063
checking permissions        0.000004
checking permissions        0.000002
checking permissions        0.000002
checking permissions        0.000003
Opening tables          0.000023
System lock             0.000035
optimizing          0.000005
statistics          0.000010
preparing           0.000008
Creating tmp table      0.000009
Sorting for group       0.000004
executing           0.000002
Copying to tmp table        0.194463
converting HEAP to MyISAM   0.042438
Copying to tmp table on disk    1.630471
Sorting result          16.097164
Sending data            0.061229
converting HEAP to MyISAM   0.805552
Sending data            7.414902
removing tmp table      1.944732
Sending data            0.000023
Waiting for query cache lock    0.000003
Sending data            0.000007
init                0.028244
optimizing          0.000026
statistics          0.000037
preparing           0.000024
executing           0.000004
Sending data            0.000539
end                 0.000008
query end           0.000005
closing tables          0.000002
removing tmp table      2.130069
closing tables          0.000028
freeing items           0.000684
logging slow query      0.000005
logging slow query      0.000004
cleaning up             0.000005

那么我该如何改进以获得体面的表现呢?您对更改选择查询或表结构有什么建议吗?我不介意对结构进行重大更改,只要它有助于提高性能

4

1 回答 1

0

一些东西。

  1. 由于您不关心 sql 注入,因此将 $filter 放在您的子选择中。
  2. 考虑对您过滤的列进行索引。

一旦你让它工作,避免sql注入。阅读 PHP:准备好的语句和存储过程

于 2013-07-18T23:56:23.090 回答