We have the following table
id # primary key
device_id_fk
auth # there's an index on it
old_auth # there's an index on it
And the following query.
$select_user = $this->db->prepare("
SELECT device_id_fk
FROM wtb_device_auths AS dv
WHERE (dv.auth= :auth OR dv.old_auth= :auth)
LIMIT 1
");
explain, I can’t reach the server of the main client, but here’s another client with fewer data
Since there’s a lot of other updates queries on auth, update queries start getting written to the slow query log and the cpu spikes
If you remove the index from auth, then the select query gets written to the slow query log, but not the update, if you add an index to device_id_fk
, it makes no difference.
I tried rewriting the query using union instead of or, but I was told that there was still cpu spike and the select query gets written to the slow query log still
$select_user = $this->db->prepare("
(SELECT device_id_fk
FROM wtb_device_auths
AS dv WHERE dv.auth= :auth)
UNION ALL
(SELECT device_id_fk
FROM wtb_device_auths AS dv
WHERE dv.old_auth= :auth)
LIMIT 1"
);
");
Explain
Most often, this is the only query in the slow query log. Is there a more optimal way to write the query? Is there a more optimal way to add indexes? The client is using an old MariaDB version, the equivalent of MYSQL 5.5, on a centos 6 server running LAMP
Additional info
The update query that gets logged to the slow query log whenever an index is added to auth
is
$update_device_auth = $this->db->prepare("UPDATE wtb_device_auths SET auth= :auth WHERE device_id_fk= :device_id_fk");
2
Answers
Your few indexes should not be slowing down your updates.
You need two indexes to make both your update and select perform well. My best guess is you never had both at the same time.
You need an index on
device_id_fk
for this update to perform well. And regardless of its index it should be declared a foreign key.You need a single combined index on
auth, old_auth
for this query to perform well.Separate
auth
andold_auth
indexes should also work well assuming there’s no too many duplicates. MySQL will merge the results from the indexes and that merge should be fast… unless a lot of rows match.If you also search for
old_auth
alone, add an index onold_auth
.And, as others have pointed out, the
select
query could return one of several matching devices with a matching auth or old_auth. This is probably bad. Ifauth
andold_auth
are intended to identify a device, add a unique constraint.Alternatively, you need to restructure your data. Multiple columns holding the same value is a red flag. It can result in a proliferation of indexes, as you’re experiencing, and also limit how many versions you can store. Instead, have just one auth per row and allow each device to have multiple rows.
Now you only need to search one column.
Now one device can have many wtb_device_auths rows. If you want the current auth for a device, search for the newest one.
Since each device will only have a few auths, this is likely to be plenty fast with the
device_id
index alone; sorting the handful of rows for a device will be fast.If not, you might need an additional combined index like
created_at, device_id
. This covers searching and sorting bycreated_at
alone as well as queries searching and sorting by bothcreated_at
anddevice_id
.OR
usually leads to a slow, full-table scan. ThisUNION
trick, together with appropriateINDEXes
is much faster:And have these “composite” indexes:
These indexes can replace the existing indexes with the same first column.
Notice that I had 3
LIMITs
; you had only 1.That
UNION ALL
involves a temp table. You should upgrade to 5.7 (at least); that version optimizes away the temp table.A
LIMIT
without anORDER BY
gives a random row; is that OK?Please provide the entire text of the slowlog entry for this one query — it has info that might be useful. If “Rows_examined” is more than 2 (or maybe 3), then something strange is going on.