I am attempting to optimise a query that displays the last reported movement for a set railroad locomotives. The query currently takes 8-12s to run for a common query to generate a report on a webpage, which outputs around 5,000 rows.
The query involves the following steps:
- From the table
t1
containing all known locomotives, select those belonging to a specific railroad (or railroads) - From the table
t2
containing movement reports, select the most recent report (highest value ofentrynumber
) referencing each entry selected fromt1
- From those movement reports selected from
t2
, select only those in the last 30 days - Join the selected reports in
t2
to a matching entry int3
, if one exists;t2
will contain a NULL entry if not.
The end result is that for each entry in t1
, the most recent report in t2
is output, unless that report is more than 30 days old.
An example query is:
SELECT t1.railroad, t1.number, t1.typeid, t2.date, t2.yard, t2.yardmaster, t2.trainsymbol, t3.trainuid
FROM locodata AS t1
LEFT JOIN locoreports AS t2 ON t2.locouid = t1.uid
LEFT JOIN tsarreports AS t3 ON t3.entrynumber = t2.tsarReportEntryNum
WHERE t1.railroad IN ('BNSF','BN','CBQ','GN','MILW')
AND t2.entrynumber IN (SELECT MAX(entrynumber) FROM locoreports GROUP BY locouid)
AND t2.date > DATE_SUB(NOW(), INTERVAL 30 DAY)
ORDER BY t1.number ASC
The tables involves are as follows:
t1
/locodata contains a unique entry for each locomotive, giving some basic information.t1
contains around 53,000 rows.t1
has its primary key on (uid
,railroad
).t2
/locoreports contains historic location reports for each locomotive, with each location report linked to the corresponding entry int1
. Each report has an associated datetime.t2
contains around 5,700,000 rows.t2
has its primary key onentrynumber
, which isAUTO_INCREMENT
, plus additional keys onlocouid
andtsarReportEntryNum
.t3
/tsarreports contains train data. Each entry int2
may link to an entry int3
; multiple entries int2
can link to the samet3
entry.t3
contains around 783,000 rows.t3
has its primary key onentrynumber
.
Using EXPLAIN
, I am very surprised to see that the search on t2
does not use any key at all, despite one being listed as available. It falls back on a full table scan, which I expect is the slow part!
+----+-------------+-------------+------------+--------+------------------+---------+---------+---------------------------------------+---------+----------+----------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------------+------------+--------+------------------+---------+---------+---------------------------------------+---------+----------+----------------------------------------------+
| 1 | PRIMARY | t2 | NULL | ALL | locouid | NULL | NULL | NULL | 5980278 | 33.33 | Using where; Using temporary; Using filesort |
| 1 | PRIMARY | t1 | NULL | ref | PRIMARY,railroad | PRIMARY | 8 | fymtraintracker.t2.locouid | 1 | 32.85 | Using where |
| 1 | PRIMARY | t3 | NULL | eq_ref | PRIMARY | PRIMARY | 4 | fymtraintracker.t2.tsarReportEntryNum | 1 | 100.00 | NULL |
| 2 | SUBQUERY | locoreports | NULL | range | locouid | locouid | 8 | NULL | 38646 | 100.00 | Using index for group-by |
+----+-------------+-------------+------------+--------+------------------+---------+---------+---------------------------------------+---------+----------+----------------------------------------------+
So, some queries:
- Am I missing an obvious key for the tables?
- Is one of the
WHERE
statements particularly horrible? The subquery in the secondWHERE
statement doesn't seem particularly nice, although theEXPLAIN
output implies it's not so bad.