我在 FreeBSD 上使用 MySQL 5.6,最近刚从使用 MyISAM 表切换到 InnoDB 以获得外键约束和事务的进步。
切换之后,我发现对一个包含 100,000 行的表的查询以前需要 0.003 秒,现在需要 3.6 秒。查询看起来像这样:
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
我注意到如果删除 ORDER BY 子句,执行时间会回落到 0.003 秒,所以问题显然出在排序上。
然后我发现,如果我添加回 ORDER BY 但删除了查询中引用的列的索引(STATUS 和 ACCESS_ID ),查询执行时间通常为 .003 秒。
然后我发现如果我在 STATUS 和 ACCESS_ID 列上加回索引,但使用 IGNORE INDEX (STATUS,ACCESS_ID) ,查询仍将在正常的 .003 秒内执行。
在我不理解的 WHERE 子句中引用索引列时,是否有关于 InnoDB 和排序结果的内容?
还是我做错了什么?
EXPLAIN 对于慢速查询 返回以下结果:
+----+-------------+-------+--------+--------------------------+---------+---------+---------------------+-------+---------------------------------------------------------------------+
| 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 快速查询 返回以下结果:
+----+-------------+-------+--------+---------------+---------+---------+----------------------+------+-------------+
| 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 |
+----+-------------+-------+--------+---------------+---------+---------+----------------------+------+-------------+
如有任何帮助,我们将不胜感激。
最佳答案
在缓慢的情况下,MySQL 假设 STATUS
上的索引将大大限制它必须排序的 users
的数量。 MySQL 是错误的。大概您的大多数用户都是 ACTIVE
。 MySQL 正在获取 50k 用户行,检查他们的 ACCESS_ID
,加入 MIGHT_FLOCK
,对结果进行排序并取出前 100 个(50k 中)。
在快速的情况下,您已经告诉 MySQL 它不能在 USERS
上使用任何一个索引。 MySQL 正在使用其下一个最佳索引,它使用 STREAK
索引(已排序)从 MIGHT_FLOCK
中获取前 100 行,然后加入到 USERS
并获取用户行,然后检查您的用户是否 ACTIVE
并且 ACCESS_ID
等于或高于 8。这要快得多,因为只有 100 行是从磁盘读取(两个表的 x2)。
我会推荐:
- 删除
STATUS
上的索引,除非您经常需要检索INACTIVE
用户(而不是ACTIVE
用户)。该索引对您没有帮助。 - 阅读this question了解为什么您的排序如此缓慢。你可能可以 tune InnoDB for better sort performance以防止此类问题。
- 如果
ACCESS_ID
为 8 或以上的用户很少,您应该已经看到了显着的改进。如果不是,您可能必须使用 STRAIGHT_JOIN在你的选择条款中。
示例如下:
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
强制 MySQL 根据您在查询中指定这两个表的顺序在 USERS
表之前访问 MIGHT_FLOCK
表.
要回答“行为为何改变”这个问题,您应该首先了解 MySQL 在每个索引上保留的统计信息:http://dev.mysql.com/doc/refman/5.6/en/myisam-index-statistics.html .如果统计信息不是最新的,或者如果 InnoDB 没有向 MySQL 提供足够的信息,查询优化器可以(并且确实)做出关于如何连接表的愚蠢决定。
关于MySQL InnoDB 索引减慢排序,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18679595/