views:

101

answers:

3

Hi, I have a table I use for high scores of an iPhone game. As of now the query is disabled so gamers are not able to see scores :-(

Table has the following fields:

Type    Collation   Attributes  Null    Default Extra   Action
id  int(11)         No      auto_increment                          
date    date            No                                  
timestamp   timestamp           No  CURRENT_TIMESTAMP                               
game_id tinyint(4)          No                                  
game_size   tinyint(4)          No                                  
game_level  tinyint(4)          No                                  
score   float           No                                  
score_string    char(11)    utf8_general_ci     No                                   
name    varchar(50) utf8_general_ci     No                                   
place   varchar(50) utf8_general_ci     No      
device  varchar(128)    utf8_general_ci     Yes NULL                

I added a two-field index for game_id and game_size This may be the problem, but I can't figure out why searches will take 5 minutes...

This is the query that takes all that time. Other, simpler queries also took long time.

SELECT SQL_CALC_FOUND_ROWS name, MIN(score_string) AS Expr1, place FROM
scores WHERE  game_id="1" AND game_size = "5" AND game_level = "1"
AND date>  "2005-04-14" GROUP BY name, place ORDER BY
MIN(score_string) Limit 0, 100;

When I test it in phpMyAdmin, it returns 1 second, then a few 0.0015 second, then 1 second again.

Any help would be more than welcome. Thanks! Hanaan

Below is EXPLAIN for proposed query:

EXPLAIN SELECT name, score_string, place
FROM scores s
WHERE game_id =1
AND game_size =15
AND game_level =1
AND id = (

SELECT id
FROM scores si
WHERE si.game_id =1
AND si.game_size =15
AND si.game_level =1
AND si.name = s.name
AND si.place = s.place
AND si.date >  '2005-04-14'
ORDER BY si.game_id, si.game_size, si.game_level, si.name, si.place, si.score_string, si.id
LIMIT 1
)
ORDER BY game_id, game_size, game_level, score_string, id

LIMIT 100

d   select_type table   type    possible_keys   key key_len ref rows    Extra
1   PRIMARY s   ref game_id,indx1,game_id_2 game_id_2   3   const,const,const   14034   Using where
2   DEPENDENT SUBQUERY  si  ref game_id,indx1,game_id_2 game_id 307 const,const,const,tmi_hcg.s.name,tmi_hcg.s.place    13  Using where

SHOW CREATE TABLE scores

CREATE TABLE `scores` (
 `id` int(11) NOT NULL auto_increment,
 `date` date NOT NULL,
 `timestamp` timestamp NOT NULL default CURRENT_TIMESTAMP,
 `game_id` tinyint(4) NOT NULL,
 `game_size` tinyint(4) NOT NULL,
 `game_level` tinyint(4) NOT NULL,
 `score` float NOT NULL,
 `score_string` char(11) NOT NULL,
 `name` varchar(50) NOT NULL,
 `place` varchar(50) NOT NULL,
 `device` varchar(128) default NULL,
 PRIMARY KEY  (`id`),
 KEY `game_id` (`game_id`,`game_size`,`game_level`,`name`,`place`,`score_string`,`id`),
 KEY `indx1` (`game_id`,`game_size`,`game_level`,`date`,`id`),
 KEY `game_id_2` (`game_id`,`game_size`,`game_level`,`score_string`,`id`)
) ENGINE=MyISAM AUTO_INCREMENT=81564 DEFAULT CHARSET=utf8

It also appears that the closer the dat is to today, the longer the response:

SELECT name, score_string, place
FROM scores s
WHERE game_id =1
AND game_size =15
AND game_level =1
AND id = ( 
SELECT id
FROM scores si
WHERE si.game_id =1
AND si.game_size =15
AND si.game_level =1
AND si.name = s.name
AND si.place = s.place
AND si.date >  "2010-10-01"
ORDER BY si.game_id, si.game_size, si.game_level, si.name, si.place, si.score_string, si.id
LIMIT 1 ) 
ORDER BY game_id, game_size, game_level, score_string, id
LIMIT 100

This took a whopping 49 seconds!

+2  A: 

Minimal: All fields in the where clause should have one index (not combined).

Maximal: Minimal + name, place, score_string as well.

It is very hard to tell without more information about number of rows, distribution of the values on the indexed fields etc.

Keep in mind: try to index the most selective field or that with an even distribution first.

Work on your accceptance rate!

Yves M.
Yves, thanks for the answer.
Hanaan Rosenthal
You mean my StackOverflow acceptance rate :-) ?
Hanaan Rosenthal
mysql is very poor at using several indexes in a where clause, mysql makes a lot better use of compound indexes in such a case (atleast for myisam, I don't know if innodb differs)
nos
A: 

That's a lot of work for an SQL query to do. Find the minimum value in one column, match other columns and get a range within another, not to mention sorting.

If you don't have any indexing, this will take a lot of time once you get a lot of records in the table. Of course, putting in too many indexes will make insert take a long time, so you don't want to overdo it.

If you run a query, and immediately rerun it, mysql will likely have the information cached, making the second (and all subsequent queries) lightning fast. I think that's why you're seeing long queries, and then they speed up. You need to use the long time to access how much new indexes will help. If you need to tell mysql to get rid of it's cache, you can use "flush tables" (but be prepared for your query to take a long time).

Hope that helps.

Todd R
Todd - Thanks, sure does. Long queries are OK for testing...
Hanaan Rosenthal
+4  A: 

Make sure you have the following indexes:

scores (game_id, game_size, game_level, date, id)
scores (game_id, game_size, game_level, name, place, score_string, id)

(either of them could be efficient depending on the data distribution).

Also, it may be useful to add the following index:

scores (game_id, game_size, game_level, score_string, id)

and rewrite the query as this:

SELECT  name, score_string, place
FROM    scores s
WHERE   game_id = 1
        AND game_size = 5
        AND game_level = 1
        AND id =
        (
        SELECT  id
        FROM    scores si
        WHERE   si.game_id = 1
                AND si.game_size = 5
                AND si.game_level = 1
                AND si.name = s.name
                AND si.place = s.place
                AND si.date > '2005-04-14'
        ORDER BY
                si.game_id, si.game_size, si.game_level, si.name, si.place, si.score_string, si.id
        LIMIT 1
        )
ORDER BY
        game_id, game_size, game_level, score_string, id
LIMIT 100

These query is identical to the first one but is more useful if your conditions are not selective.

These two articles explain how the query works:

Quassnoi
Quassnoi - This returns an error:
Hanaan Rosenthal
See below......
Hanaan Rosenthal
SELECT name, score_string, placeFROM scores sWHERE id = (SELECT idFROM scores siWHERE si.game_id =1AND si.game_size =5AND si.game_level =1AND si.name = s.nameAND si.place = s.placeAND si.date > '2005-04-14'ORDER BY si.game_id, si.game_size, si.game_level, si.name, si.place, si.score_string, si.idLIMIT 1)WHERE game_id =1AND game_size =5AND game_level =1ORDER BY game_id, game_size, game_level, score_string, idLIMIT 100
Hanaan Rosenthal
MySQL said: #1064 - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'WHERE game_id = 1 AND game_size = 5 AND game_level = 1ORDE' at line 17
Hanaan Rosenthal
@Hanaan: try now.
Quassnoi
Quassnoi - This works, but still takes few seconds to execute :-(
Hanaan Rosenthal
@Hanaan: did you create the indexes I suggested? Please post the output of `SHOW CREATE TABLE scores` as an update of your post; also please post the output of `EXPLAIN SELECT …` of my query.
Quassnoi
@Quassnoi - I did create the three indexes, and one by one. Below is EXPLAIN
Hanaan Rosenthal
id select_type table type possible_keys key key_len ref rows Extra1 PRIMARY s range game_id game_id 3 NULL 27977 Using where; Using index; Using filesort2 DEPENDENT SUBQUERY si ref game_id game_id 307 const,const,const,tmi_hcg.s.name,tmi_hcg.s.place 13 Using where
Hanaan Rosenthal
OK - removed all indexes and recreated. Now takes 0.4 second. More reasonable.
Hanaan Rosenthal
@Hanaan: please submit it as an update to your post. It's impossible to comprehend it in a comment.
Quassnoi
My problem is that it was OK before, just when I make it available to thousands of queries a day (not much, but good sample) suddenly query times spike and I have no idea why...
Hanaan Rosenthal
@Hanaan: most probably it's because your queries used a quite resource-intensive `filesort`. The last one does not.
Quassnoi
OK - I will give it a shot!
Hanaan Rosenthal
@Hanaan: please also post the output of `SHOW CREATE TABLE scores`, so that I can see which names did your indexes receive and find them in the plan.
Quassnoi
@Quassnoi - see my now edited post.
Hanaan Rosenthal
@Hanaan: yes, the plan is exactly as it should be.
Quassnoi
@Quassnoi: The delay happens when the dates are closer to today. I ran the same query with '10-01-2010' as the date and it took 49 seconds...
Hanaan Rosenthal
@Hanaan: when the dates are close to today, you should use your original query (with `GROUP BY`) and make sure `indx1` is used in the plan. `MySQL`'s optimizer is not so smart to select the correct plan automatically. On `PHP` side, you will need to estimate the difference between the search date and today, and choose the query depending on the difference. The exact value of the threshold is a matter of trial and error: run several queries with different dates and compare the execution times.
Quassnoi
OK - implemented. If over 30 days, I use the original, if longer, I use your query. Looking forward to hearing from the host about the length of the queries...
Hanaan Rosenthal
@Quassnoi: Thanks so much for all the help - truly generous.
Hanaan Rosenthal
@Hanaan: it is considered a good gesture here to mark an answer that answered your question as accepted. This will raise your accept rate and will make the users more inclined to help you in the future.
Quassnoi
Of course - was still deep in making this work, but this was certainly big help and the right solution. Thanks, and hope to be able to help you someday.
Hanaan Rosenthal
@Quassnoi - sorry, still new here... How do I mark it? I saw it earlier and slips me now...
Hanaan Rosenthal
@Hanaan: there's a checkmark icon right under the count of votes for the answer, click it.
Quassnoi