From the docs:
In some cases, MySQL cannot use indexes to resolve the ORDER BY, although it still uses indexes to find the rows that match the WHERE clause. These cases include the following:
...snip...
The key used to fetch the rows is not the same as the one used in the ORDER BY:
SELECT * FROM t1 WHERE key2=constant ORDER BY key1;
And:
With
EXPLAIN SELECT ... ORDER BY
, you can check whether MySQL can use indexes to resolve the query. It cannot if you seeUsing filesort
in theExtra
column.
Your query plan confirms that your slow query is using the queued_at
key. If you remove the ORDER BY
, the query plan should use the worker_id
key instead. One possible reason for the difference in speed is the difference in which key is being used.
As Peter Zaitsev says in MySQL Performance Blog: ORDER BY ... LIMIT Performance Optimization:
It is very important to have ORDER BY with LIMIT executed without scanning and sorting full result set, so it is important for it to use index...
For example if I do
SELECT * FROM sites ORDER BY date_created DESC LIMIT 10;
I would use index on (date_created) to get result set very fast.Now what if I have something like
SELECT * FROM sites WHERE category_id=5 ORDER BY date_created DESC LIMIT 10;
In this case index by date_created may also work but it might not be the most efficient – If it is rare category large portion of table may be scanned to find 10 rows. So index on (category_id, date_created) will be better idea.
You could try, per this suggestion, creating a composite index (worker_id, queued_at)
for use with this specific query. If for some reason you can't add another index, you could also try forcing your ordered query to use the worker_id
index, to narrow the result set before sorting.
It would be great if you could rewrite this query so that you could find the single row you want without the ORDER BY
, since MySQL will order the result before applying LIMIT 1
. But not knowing more about your broad goals here, I can't say whether that would be possible. What about splitting the task into the following two queries?
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;
Or as a subquery, if you don't have issues with duplicate values?
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);