Simple SQL query takes 10 to 20 times longer with "ORDER BY". How can I speed it up?
My first query was:
SELECT *
FROM wp_usermeta
WHERE meta_key = 'partner'
AND meta_value = 1
ORDER BY user_id DESC
LIMIT 5
It takes 0.2601 seconds. After some research I could optimize it to:
SELECT user_id
FROM wp_usermeta
WHERE meta_key = 'partner'
AND meta_value = '1'
ORDER BY umeta_id DESC
LIMIT 5
This query takes just 0.1491 seconds, but still too much. If I remove the ORDER BY, it takes only 0.0075 seconds.
I read a lot on Stackoverflow and other forums, but I could not get a better output. Has someone an idea?
It is a standard WordPress usermeta table.
The wp_usermeta table in WordPress is well-known and it has a single-column index on meta_key.
But this selects all rows with the specified key, which doesn't narrow down the search much. Also it doesn't help sorting, so the query must do extra work to do the sorting:
mysql> explain SELECT * FROM wp_usermeta WHERE meta_key = 'partner' AND meta_value = 1 ORDER BY user_id DESC LIMIT 5\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: wp_usermeta
type: ref
possible_keys: meta_key
key: meta_key
key_len: 767
ref: const
rows: 1
Extra: Using where; Using filesort
Adding a new index should help:
mysql> alter table wp_usermeta add key (meta_key(191), meta_value(191), user_id);
mysql> explain SELECT * FROM wp_usermeta WHERE meta_key = 'partner' AND meta_value = 1 ORDER BY user_id DESC LIMIT 5\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: wp_usermeta
type: ref
possible_keys: meta_key_2,meta_key
key: meta_key_2
key_len: 767
ref: const
rows: 1
Extra: Using where; Using filesort
Even though this shows it's using the new index (meta_key_2), it's not helping. The key_len and ref indicate it's only using the first column of the index. Why can't it use both columns?
Because your query compares the integer value 1 to a string column meta_value. You must compare similar types, i.e. string '1' to the string column:
mysql> explain SELECT * FROM wp_usermeta WHERE meta_key = 'partner' AND meta_value = '1' ORDER BY user_id DESC LIMIT 5\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: wp_usermeta
type: ref
possible_keys: meta_key_2,meta_key
key: meta_key_2
key_len: 1534
ref: const,const
rows: 1
Extra: Using where
Now it's able to use the second column in the index to search for value '1', you can tell because key_len: 1534 and ref: const,const indicate it's using two columns of the index instead of one column.
Then the optimizer realizes it's already reading the data in order by user_id, so there's no need to sort. The "Using filesort" goes away.
WP has an inefficient schema for its "meta" tables. But they can be fixed. In [here], I discuss several things that need fixing. And I explain the 191 kludge, plus 5 options for avoiding it.
And I don't get into "index merge intersect", since a composite index is always(?) better.
Related
As title suggests, should the EXPLAIN output change after explicitly using FORCE INDEX (index_1, index_2) in a query?
As an example I have the following query:
select
person_id,
role_id,
scope_id,
count(distinct qualification_id) as ncomps
from dw_rolepersonqualification
where ((mandatory = 'y') and (expiry_date > now()))
group by 1, 2, 3
When I run it with EXPLAIN, I get:
id: 1
select_type: SIMPLE
table: dw_rolepersonqualification
type: ALL
possible_keys: PRIMARY, idx_person, idx_role, idx_qualification, idx_scope, idx_mandatory
key: null
key_len: null
ref: null
rows: 8267852
Extra: Using where; Using filesort
When I add in FORCE INDEX (dx_person, idx_role, idx_qualification, idx_scope) it does not change the output of EXPLAIN. Is this to be expected or am I missing something?
The optimal index for that query is
INDEX(mandatory, -- tested with "=", so first
expiry_date) -- a range
Even so, it may decide that the index is not worth the effort. If the Optimizer estimates that more than ~20% of the table matches the WHERE clause, it will decide to scan the table rather than bouncing between the index's BTree and the data BTree.
A "covering" index may (or may not) be better:
INDEX(mandatory, -- tested with "=", so first
expiry_date, -- a range
person_id, role_id, scope_id,
qualification_id) -- all other touched columns (any order)
(Caveat: Some of what I say may not be valid; please provide SHOW CREATE TABLE.)
My Mantra: "Force Index may help today, but hurt tomorrow."
I'm running a query that spans 3 tables, none of which have > 55K rows. This query is taking 20+ seconds to run, which seems excessive:
SELECT
`cp`.`author`,
`cc`.`contents`
FROM
`challenge_properties` as `cp`,
`challenges` as `c`,
`challenge_contents` as `cc`
WHERE
`cp`.`followup_id` = `c`.`latest_followup` AND
`cp`.`status` = 'new' AND
`c`.`id` = `cp`.`challenge_id` AND
`c`.`id` = `cc`.`challenge_id`
This is the result of EXPLAINing that query:
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: c
type: ALL
possible_keys: PRIMARY,latest_followup_index
key: NULL
key_len: NULL
ref: NULL
rows: 13817
Extra:
*************************** 2. row ***************************
id: 1
select_type: SIMPLE
table: cc
type: ref
possible_keys: challenge_id
key: challenge_id
key_len: 5
ref: cts.c.id
rows: 1
Extra: Using where
*************************** 3. row ***************************
id: 1
select_type: SIMPLE
table: cp
type: ref
possible_keys: challenge_id,followup_id
key: followup_id
key_len: 5
ref: cts.c.latest_followup
rows: 1
Extra: Using where
As you can see, the first table, challenges has a primary key, but it's not being used. I've tried adding the FORCE KEY(PRIMARY) clause to the challenges table declaration, but it's still not used.
What can I do to speed up this query? Thanks.
Your query is selecting ALL records from the challenges table — therefore there is no need to use any index on the records from that table. Basically MySQL is selecting every record in challenges, then finding matching records in the other two tables.
Couldn't you just leave out the challenges table all together? You're not selecting data from that table, and the only time that table would limit the data selected would be if your other tables had invalid challenge_ids, which foreign keys can take care of...
SELECT
`cp`.`author`,
`cc`.`contents`
FROM
`challenge_properties` as `cp`,
`challenge_contents` as `cc`
WHERE
`cp`.`status` = 'new' AND
`cp`.`challenge_id` = `cc`.`challenge_id`
EDIT: You say you can't remove the challenges table from the query... I would try specifying your JOIN conditions in the JOIN clause instead of the WHERE:
SELECT
`cp`.`author`,
`cc`.`contents`
FROM `challenge_properties` as `cp`
JOIN `challenges` as `c`
ON `cp`.`challenge_id` = `c`.`id`
AND `cp`.`followup_id` = `c`.`latest_followup`
JOIN `challenge_contents` as `cc`
ON `cc`.`challenge_id` = `c`.`id`
WHERE `cp`.`status` = 'new'
The query optimizer might already do this for you, but it doesn't hurt to try it, and I think it's easier to see how the joins are happening with this syntax.
You could also try adding another index to challenge_properties on ( challenge_id, followup_id ), and another to challenges on ( challenge_id, latest_followup ) — the complex key might help MySQL work quicker. But it's also possible that the problem might be outside your query... usually when you EXPLAIN and see only one table with big numbers in the rows column, your query is pretty well optimized. MySQL is only looking at one row in challenge_properties and one row in challenge_contents, and scanning every row in challenges to find a match.
EDIT 2:
Unfortunately, I'm not sure what else can be done to optimize this query. You can get slightly more performance if the indexes used (cc.challenge_id and cp.followup_id) are UNIQUE NOT NULL indexes, and you should get better performance with a complex index for cp on (cp.challenge_id, cp.followup_id). This would turn those type: ref into type: eq_ref, which is slightly better. But that's about it... do you not have problems with any other queries? Your query should theoretically return 13817 rows... is the amount of data possibly the problem? Does it speed up significantly if you just select COUNT(*) instead of returning all the rows?
Explain
SELECT *
FROM `EventTimes`
LEFT JOIN Events on event_id=Events.id
WHERE festival_id = 12
ORDER BY time;
for Events table I have these fields:
id
name
festival_id
, etc.
for EventTimes table I have:
id
event_id
time
, etc.
I create index for EventTimes:create index eventid_time on EventTimes (event_id, time)
I also create an index for Events:create index ev_festivalid on Events (festival_id)
but I get
for Events:
Select_type: Simple
Table : Events
Type : ref
possible_keys: PRIMARY,ev_festivalid
key : ev_festivalid
key_len: 5
ref: const
rows : 14
Extra : Using where; Using temporary; Using filesort
for EventTimes:
Select_type: Simple
Table : EventTimes
Type : ref
possible_keys: eventid_time
key : eventid_time
key_len: 5
ref: dbname.Events.id
rows : 1
Extra : Using where
How to avoid to have Using temporary; Using filesort in Events?
The reason for the filesort is that the rows come back ordered by festival_id, and you are re-ordering them by time, so mysql needs to re-sort the results. I don't see a simple way around that given your schema and query. If I were you I wouldn't be too worried about the filesort. It's really not as bad as it sounds. Read here for more info.
I should also point out that your use of LEFT JOIN doesn't make sense. Since your WHERE clause references the optional table, it negates the outer join. You should consider re-writing the query.
I have this query:
SELECT * FROM table WHERE x >= 500 AND x < 5000 ORDER BY date DESC LIMIT 0,50
I have index: x, date - Btree
Why is this query using index and filesort, if I have index on both values.
x= integer
date = date
tyble type = myisam
explain:
ID: 1
select_type: SIMPLE
table: d
type: range
possible_keys: sort
key: sort
key_len: 2
ref: null
rows: 198
extra: using index condition; using filesort
The query is using filesort because it is a range query. Filesort would desappear if the query used exact equation.
But you probably know that filesort is actualy a misname and has actually no relation to files.
From the reference -
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:
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;
Try to add index INDEX (date, x).
I've been trying and googling everything and still can't figure out what's going on.
I have a big table (100M+rows). Among others it has 3 columns: user_id, date, type.
It has an index idx(user_id, type, date).
When I EXPLAIN this query:
SELECT *
FROM table
WHERE user_id = 12345
AND type = 'X'
ORDER BY date DESC
LIMIT 5
EXPLAIN shows that MySQL examined 110K rows. which is roughly row many rows this user_id has.
My question is:
Why the same index is not used for ORDER_BY LIMIT 5? It knows which rows belong to the user_id, date is part of the same index, so why not just take last 5 rows in that index?
P.S. I tried index by (user_id, date, type) - same results; i tried removing DESC - same results.
This is the EXPLAIN plan:
id: 1
select_type: SIMPLE
table: s
type: ref
possible_keys: dateIdx,userTypeDateIdx
key: userTypeDateIdx
key_len: 5
ref: const,const
rows: 110118
Extra: Using where
I also tried adding FORCE INDEX FOR ORDER BY hint, but i still get rows: 110118.
Did you ANALYZE TABLE after creating the index?
Mysql will not use the index until the table is analyzed. The best index to use is the one you created with (user_id, type, date)
The date in the index is in ascending order, and you are asking for the most recent five rows in descending order by date; it can't use the index for that. If you changed the index to user_id, type, date desc it would be able to use the index to get the most recent five rows.