我试图改进我的查询,以便它不会花这么长时间。有什么我可以尝试的吗?
我正在使用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_at
和worker_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
答案 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
列定义为BIGINT
与INT
或SMALLINT
或{{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);