为什么我的ORDER BY BIGINT(20)需要这么长时间?

时间:2014-04-23 23:15:41

标签: mysql sql optimization indexing query-optimization

我试图改进我的查询,以便它不会花这么长时间。有什么我可以尝试的吗?

我正在使用InnoDB。

我的表:

mysql> describe hunted_place_review_external_urls;
+--------------+--------------+------+-----+---------+----------------+
| Field        | Type         | Null | Key | Default | Extra          |
+--------------+--------------+------+-----+---------+----------------+
| id           | bigint(20)   | NO   | PRI | NULL    | auto_increment |
| worker_id    | varchar(255) | YES  | MUL | NULL    |                |
| queued_at    | bigint(20)   | YES  | MUL | NULL    |                |
| external_url | varchar(255) | NO   |     | NULL    |                |
| place_id     | varchar(63)  | NO   | MUL | NULL    |                |
| source_id    | varchar(63)  | NO   |     | NULL    |                |
| successful   | tinyint(1)   | NO   |     | 0       |                |
+--------------+--------------+------+-----+---------+----------------+

我的查询:

mysql> select * from hunted_place_review_external_urls where worker_id is null order by queued_at asc limit 1;

1 row in set (4.00 sec)

mysql> select count(*) from hunted_place_review_external_urls where worker_id is null;
+----------+
| count(*) |
+----------+
|    19121 |
+----------+
1 row in set (0.00 sec)

即使我在queued_atworker_id上有索引,为什么还要花4?

以下是此查询的EXPLAIN

mysql> explain select * from hunted_place_review_external_urls where worker_id is null order by queued_at asc limit 1;
+----+-------------+-----------------------------------+-------+---------------+-----------+---------+------+------+-------------+
| id | select_type | table                             | type  | possible_keys | key       | key_len | ref  | rows | Extra       |
+----+-------------+-----------------------------------+-------+---------------+-----------+---------+------+------+-------------+
|  1 | SIMPLE      | hunted_place_review_external_urls | index | worker_id     | queued_at | 9       | NULL |   67 | Using where |
+----+-------------+-----------------------------------+-------+---------------+-----------+---------+------+------+-------------+
1 row in set (0.00 sec)

删除order by queued_at部分后,速度会快得多:

mysql> select * from hunted_place_review_external_urls where worker_id is null limit 1;

1 row in set (0.00 sec)

count(*)越小,速度也越快:

mysql> select count(*) from hunted_place_review_external_urls where worker_id is null;
+----------+
| count(*) |
+----------+
|    10    |
+----------+
1 row in set (0.00 sec)

mysql> select * from hunted_place_review_external_urls where worker_id is null order by queued_at asc limit 1;

1 row in set (0.00 sec)

我的queued_at值是以毫秒数表示的时间戳,例如1398210069531

2 个答案:

答案 0 :(得分:2)

MySQL使用queued_at索引来避免“使用filesort”操作。似乎MySQL正在查看表中的每一行,这需要四秒钟。

MySQL正在使用索引首先获取具有最低值queued_at的行,然后访问基础数据页以检查worker_id是否为NULL。 MySQL通过索引工作,从queued_at的最低值到最高值。

对于找到的每个匹配行,MySQL将该行添加到结果集。

请注意,只有在 all 找到匹配的行并准备好结果集之后才会应用LIMIT子句。 (当找到第一个匹配的行时,没有“早出”,MySQL仍然会通过每一行来查找它们中的每一行。但至少,MySQL正在避免可能是昂贵的Using filesort操作获取订购的行。)

您的其他查询表现出更好的性能,因为它们具有不同的访问计划,这可能会使用索引来限制需要检查的行数。


要提高此特定查询的效果,您可以尝试添加索引:

... ON hunted_place_review_external_urls (worker_id, queued_at);

如果这不是一个选项,您可以尝试使用索引提示来影响优化器使用不同的索引:

  select * 
    from hunted_place_review_external_urls USING INDEX `worker_id`
   where worker_id is null 
   order by queued_at asc
   limit 1;

请注意,USING INDEX提示引用索引的名称,而不是列的名称。从EXPLAIN输出中,看起来有一个名为“worker_id”的索引。我猜这个索引是在名为“worker_id”的列上,但这只是猜测。


除此之外,这与queued_at列定义为BIGINTINTSMALLINT或{{1}无关}。

答案 1 :(得分:1)

来自the docs

  

在某些情况下,MySQL无法使用索引来解析ORDER BY,   虽然它仍然使用索引来查找与WHERE匹配的行   条款。这些案例包括以下内容:

     

...略...

     

用于获取行的密钥与使用的密钥不同   订购:

     

SELECT * FROM t1 WHERE key2 = constant ORDER BY key1;

  

使用EXPLAIN SELECT ... ORDER BY,您可以检查MySQL是否可以使用   用于解析查询的索引。如果您看到Using filesort,则无法执行此操作   Extra列。

您的查询计划确认您的慢查询使用的是queued_at密钥。如果您删除ORDER BY,则查询计划应使用worker_id密钥。速度差异的一个可能原因是使用密钥的差异。

Peter Zaitsev在MySQL Performance Blog: ORDER BY ... LIMIT Performance Optimization中说:

  

在没有扫描和排序完整结果集的情况下执行ORDER BY和LIMIT非常重要,因此使用索引很重要...

     

例如,如果我SELECT * FROM sites ORDER BY date_created DESC LIMIT 10;,我会使用index on(date_created)来快速获得结果集。

     

现在如果我有类似SELECT * FROM sites WHERE category_id=5 ORDER BY date_created DESC LIMIT 10;

的内容怎么办?      

在这种情况下,由date_created索引也可能有效,但它可能效率不高 - 如果它很少,则可以扫描大部分表格以查找10行。所以(category_id,date_created)上的索引会更好。

根据此建议,您可以尝试创建一个复合索引(worker_id, queued_at),以便与此特定查询一起使用。如果由于某种原因您无法添加其他索引,您还可以尝试强制您的有序查询使用worker_id索引,以便在排序之前缩小结果集。

如果您可以重写此查询以便在没有ORDER BY的情况下找到所需的单行,那将会很棒,因为MySQL会在应用LIMIT 1之前对结果进行排序。但是在这里不太了解你的广泛目标,我不能说这是否可行。将任务拆分为以下两个查询怎么样?

select MIN(queued_at) from hunted_place_review_external_urls where worker_id is null into @var;

select * from hunted_place_review_external_urls where worker_id is null and queued_at = @var;

或者作为子查询,如果您没有重复值的问题?

select * from hunted_place_review_external_urls where queued_at in (select MIN(queued_at) from hunted_place_review_external_urls where worker_id is null);