简体   繁体   中英

SQL query is very slow for certain parameters (MySQL)

I am making a PHP backend API which executes a query on MySQL database. This is the query:

SELECT * FROM $TABLE_GAMES WHERE 
 ($GAME_RECEIVERID = '$userId'OR $GAME_OTHERID = '$userId')
  ORDER BY $GAME_ID LIMIT 1"

Essentially, I'm passing $userId as parameter, and getting row with smallest $GAME_ID value and it would return result in less than 100 ms for users that have around 30 000 matching rows in table. However, I have since added new users, that have around <100 matching rows, and query is painfully slow for them, taking around 20-30 seconds every time.

I'm puzzled to why the query is so much slower in situations where it is supposed to return low amount of rows, and extremely fast when returns huge amount of rows especially since I have ORDER BY .

I have read about parameter sniffing, but as far as I know, that's the SQL Server thing, and I'm using MySQL .

EDIT

Here is the SHOW CREATE statement:

CREATE TABLE games ( ID int(11) NOT NULL AUTO_INCREMENT, SenderID int(11) NOT NULL, ReceiverID int(11) NOT NULL, OtherID int(11) NOT NULL, Timestamp timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY ( ID ) ) ENGINE=MyISAM AUTO_INCREMENT=17275279 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci

Here is the output of EXPLAIN

+----+-------------+-------+------+---------------+------+---------+-----+------+-------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | extra | +----+-------------+-------+------+---------------+------+---------+-----+------+-------+ | 1 | SIMPLE | games | NULL | index | NULL | PRIMARY | 4 | NULL | 1 | +----+-------------+-------+------+---------------+------+---------+-----+------+-------+

id select_type table partitions type possible_keys key key_len ref rows filtered Extra
1 SIMPLE games NULL index NULL PRIMARY 4 NULL 1 19.00 Using where

I tried prepared statement, but still getting the same result.

Sorry for poor formatting, I'm still noob at this.

You need to use EXPLAIN to analyse the performance of the query.

ie

 EXPLAIN SELECT * FROM $TABLE_GAMES WHERE 
  ($GAME_RECEIVERID = '$userId'OR $GAME_OTHERID = '$userId') 
   ORDER BY $GAME_ID LIMIT 1"

The EXPLAIN would provide the information about the select query with execution plan. It is great tool to identify the slowness in the query. Based on the obtained information you can create the Indexes for the columns used in WHERE clause .

CREATE INDEX index_name ON table_name (column_list)

This would definitely increase the performance of the query.

Your query is being slow because it cannot find a matching record fast enough. With users where a lot of rows match, chances of finding a record to return are much higher, all other things being equal.

That behavior appears when $GAME_RECEIVERID and $GAME_OTHERID aren't part of an index, prompting MySQL to use the index on $GAME_ID because of the ordering. However, since newer players have not played the early games, there are literally millions of rows that won't match, but have to be checked nonetheless.

Unfortunately, this is bound to get worse even for old users, as your database grows. Ideally, you will add indexes on $GAME_RECEIVERID and $GAME_OTHERID - something like:

ALTER TABLE games
ADD INDEX receiver (ReceiverID),
ADD INDEX other (OtherID)

PS: Altering a 17 million rows table is going to take a while, so make sure to do it during a maintenance window or similar if this is used in production.

Is this the query after the interpolation? That is, is this what MySQL will see?

SELECT * FROM GAMES
     WHERE RECEIVERID = '123'
        OR OTHERID    = '123'
     ORDER BY ID LIMIT 1

Then this will run fast, regardless:

SELECT *
    FROM GAMES
    WHERE ID = LEAST(
         ( SELECT MIN(ID) FROM GAMES WHERE RECEIVERID = '123' ),
         ( SELECT MIN(ID) FROM GAMES WHERE OTHERID    = '123' )
                    );

But, you will need both of these:

INDEX(RECEIVERID, ID),
INDEX(OTHERID, ID)

Your version of the query is scanning the table until it finds a matching row. My version will

  1. make two indexed lookups;
  2. fetch the other columns for the one row.

It will be the same, fast, speed regardless of how many rows there are for USERID.

(Recommend switching to InnoDB.)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM