Question

I am using MySQL 5.6 on FreeBSD and have just recently switched from using MyISAM tables to InnoDB to gain advances of foreign key constraints and transactions.

After the switch, I discovered that a query on a table with 100,000 rows that was previously taking .003 seconds, was now taking 3.6 seconds. The query looked like this:

SELECT *
    -> FROM USERS u 
    -> JOIN MIGHT_FLOCK mf ON (u.USER_ID = mf.USER_ID) 
    ->  WHERE u.STATUS = 'ACTIVE' AND u.ACCESS_ID >= 8  ORDER BY mf.STREAK DESC LIMIT 0,100

I noticed that if I removed the ORDER BY clause, the execution time dropped back down to .003 seconds, so the problem is obviously in the sorting.

I then discovered that if I added back the ORDER BY but removed indexes on the columns referred to in the query (STATUS and ACCESS_ID), the query execution time would take the normal .003 seconds.

Then I discovered that if I added back the indexes on the STATUS and ACCESS_ID columns, but used IGNORE INDEX (STATUS,ACCESS_ID), the query would still execute in the normal .003 seconds.

Is there something about InnoDB and sorting results when referencing an indexed column in a WHERE clause that I don't understand?

Or am I doing something wrong?

EXPLAIN for the slow query returns the following results:

+----+-------------+-------+--------+--------------------------+---------+---------+---------------------+-------+---------------------------------------------------------------------+
| id | select_type | table | type   | possible_keys            | key     | key_len | ref                 | rows  | Extra                                                               |
+----+-------------+-------+--------+--------------------------+---------+---------+---------------------+-------+---------------------------------------------------------------------+
|  1 | SIMPLE      | u     | ref    | PRIMARY,STATUS,ACCESS_ID | STATUS  | 2       | const               | 53902 | Using index condition; Using where; Using temporary; Using filesort |
|  1 | SIMPLE      | mf    | eq_ref | PRIMARY                  | PRIMARY | 4       | PRO_MIGHT.u.USER_ID |     1 | NULL                                                                |
+----+-------------+-------+--------+--------------------------+---------+---------+---------------------+-------+---------------------------------------------------------------------+

EXPLAIN for the fast query returns the following results:

+----+-------------+-------+--------+---------------+---------+---------+----------------------+------+-------------+
| id | select_type | table | type   | possible_keys | key     | key_len | ref                  | rows | Extra       |
+----+-------------+-------+--------+---------------+---------+---------+----------------------+------+-------------+
|  1 | SIMPLE      | mf    | index  | PRIMARY       | STREAK  | 2       | NULL                 |  100 | NULL        |
|  1 | SIMPLE      | u     | eq_ref | PRIMARY       | PRIMARY | 4       | PRO_MIGHT.mf.USER_ID |    1 | Using where |
+----+-------------+-------+--------+---------------+---------+---------+----------------------+------+-------------+

Any help would be greatly appreciated.

Was it helpful?

Solution

In the slow case, MySQL is making an assumption that the index on STATUS will greatly limit the number of users it has to sort through. MySQL is wrong. Presumably most of your users are ACTIVE. MySQL is picking up 50k user rows, checking their ACCESS_ID, joining to MIGHT_FLOCK, sorting the results and taking the first 100 (out of 50k).

In the fast case, you have told MySQL it can't use either index on USERS. MySQL is using its next-best index, it is taking the first 100 rows from MIGHT_FLOCK using the STREAK index (which is already sorted), then joining to USERS and picking up the user rows, then checking that your users are ACTIVE and have an ACCESS_ID at or above 8. This is much faster because only 100 rows are read from disk (x2 for the two tables).

I would recommend:

  • drop the index on STATUS unless you frequently need to retrieve INACTIVE users (not ACTIVE users). This index is not helping you.
  • Read this question to understand why your sorts are so slow. You can probably tune InnoDB for better sort performance to prevent these kind of problems.
  • If you have very few users with ACCESS_ID at or above 8 you should see a dramatic improvement already. If not you might have to use STRAIGHT_JOIN in your select clause.

Example below:

SELECT *
FROM MIGHT_FLOCK mf 
STRAIGHT_JOIN USERS u ON (u.USER_ID = mf.USER_ID) 
WHERE u.STATUS = 'ACTIVE' AND u.ACCESS_ID >= 8  ORDER BY mf.STREAK DESC LIMIT 0,100

STRAIGHT_JOIN forces MySQL to access the MIGHT_FLOCK table before the USERS table based on the order in which you specify those two tables in the query.

To answer the question "Why did the behaviour change" you should start by understanding the statistics that MySQL keeps on each index: http://dev.mysql.com/doc/refman/5.6/en/myisam-index-statistics.html. If statistics are not up to date or if InnoDB is not providing sufficient information to MySQL, the query optimiser can (and does) make stupid decisions about how to join tables.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top