MySQL InnoDB 索引减慢排序

标签 mysql performance sorting innodb myisam

我在 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 但删除了查询中引用的列的索引(STATUSACCESS_ID ),查询执行时间通常为 .003 秒。

然后我发现如果我在 STATUSACCESS_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/

相关文章:

performance - ASP.NET MVC : how is this possible ? 参数字典包含参数 'x' 的空条目

java - 如何在 Java 8 中使用 lambda 将字符串排序为整数

java - 在java中按优先级队列对学生的分数进行排序

php - 在 foreach 循环中从数据库中“加载更多”数据

php - 只抓取 x 个字符 - 怎么做?

MYSQL:选择字段值的总和同时还选择唯一值?

java - Nashorn 启动慢可以克服吗?

mysql - 分发一个MySQL协议(protocol)?

java - 实现 SwingWorker

C# 使用 IComparer 对 x 列进行排序