2013-07-18 2 views
2

mysql의 최고 점수 목록에 큰 성능 문제가 있습니다. 기본적으로 우리에게는 사용자가 있고, 우리는 게임을 가지고 있으며 사용자는 게임을하고 그 게임에서 점수를 얻을 수 있습니다. 사용자 정의 필터로 탐색 할 수있는 뚜껑 목록을 원하므로 사용자는 게임 X 나 다른 요인으로 모든 점수를 표시하거나 필터링 할 수 있습니다. 불행히도 현재 목록은 전혀 실행되지 않습니다. 그것은 현재 50 만개의 데이터 세트를 가지고 있으며, 우리의 선택 쿼리는 20 초 이상 걸리고 너무 길다.MySQL에서 scorelist 테이블을 만들려고했지만 성능이 좋지 않습니다.

이 현재 테이블 구조입니다 :

우리의 선택 쿼리는 다음과 같습니다
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; 

: 한도 (선택 쿼리의

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 

프로파일 :

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) 

이 선택 쿼리를 설명 "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 

그럼 어떻게 성능을 향상시킬 수 있습니까? 테이블 구조에 대한 선택 쿼리 또는 변경 사항에 대한 제안 사항이 있습니까? 구조에 큰 변화가 없다면 성능에 도움이된다면

답변

0

몇 가지가 있습니다.

  1. SQL 삽입에 신경 쓰지 않으므로 하위 선택 안에 $ filter를 넣으십시오.
  2. 필터링 대상 열의 색인 생성을 고려하십시오.

일단 작동하게되면 SQL 주입을 피하십시오. Read up on PHP: Prepared statements and stored procedures

+0

나는 실제로 SQL 주입에 신경 쓰고있다! '$ filter'에 대한 한 가지 예는'WHERE users.country = : country'와 : country가 나중에 바인딩 된 것입니다. 인덱스 설정에 대한 팁을 주셔서 감사합니다. 'ORDER BY' 행 ('score')에 대해서'WHERE'를 사용하거나'ORDER BY' 행에 대해서만 인덱스를 설정해야합니까? – user2015253

+0

또한 subselect에 조인되지 않은 행을 포함 할 수 있으므로'$ filter'를 subselect에 넣을 수 없습니다. subselect 안에 모든 조인을 이동하는 것이 영리한 것인지 확실하지 않습니다. 내가 틀렸다면 알려줘! – user2015253

관련 문제