I have a query similar to the following:
FROM example_table
WHERE
`date` BETWEEN '2023-11-26' AND '2023-11-28'
AND location_id IN (3, 4, 6, 7, 8, 10, 11, 12, 14, 18, 19, 22, 23, 24, 28, 29, 30, 31, 32, 36, 39, 40, 41, 43, 45, 46, 48, 49, 50, 51, 52, 54, 55, 56, 57, 59, 60, 61, 62, 68, 69, 75, 121)
AND ( `type` IS NULL OR ( `type` IN ('type1', 'type2', 'type3') ) )
GROUP BY location_id;
My understanding is that, while creating a multi column index, the column with higher cardinality / selectivity goes first. I tried testing the performance with two index keys:
- (date, location_id, type, amount)
- (location_id, date, type, amount)
In my actual table, I have 11,833 unique values in the date column, and only 99 in location_id. Currently, there are 63+ million rows.
Nevertheless, MySQL 8 prefers to use the one starting with location_id. Even when I try FORCE INDEX
and EXPLAIN ANALYZE
, it shows a higher cost / time on the one starting with date
.
What could be going on?
EDIT:
EXPLAIN ANALYZE:
- date first index
-> Group aggregate: sum(ledger_entries.amount_cents) (cost=1897 rows=6236) (actual time=0.167..4.67 rows=43 loops=1)
-> Filter: ((ledger_entries.`date` = DATE'2023-11-28') and (ledger_entries.location_id in (3,4,6,7,8,10,11,12,14,18,19,22,23,24,28,29,30,31,32,36,39,40,41,43,45,46,48,49,50,51,52,54,55,56,57,59,60,61,62,68,69,75,121)) and ((ledger_entries.`type` is null) or (ledger_entries.`type` in ('Procedure','Adjustment','AncillarySale')))) (cost=1273 rows=6236) (actual time=0.0221..4.09 rows=6192 loops=1)
-> Covering index range scan on ledger_entries using index_le_date_location_type_amount_cents over (date = '2023-11-28' AND location_id = 3 AND type = NULL) OR (date = '2023-11-28' AND location_id = 3 AND type = 'Adjustment') OR (170 more) (cost=1273 rows=6236) (actual time=0.02..2.83 rows=6192 loops=1)
- location first index
-> Group aggregate: sum(ledger_entries.amount_cents) (cost=1888 rows=6236) (actual time=0.171..4.74 rows=43 loops=1)
-> Filter: ((ledger_entries.`date` = DATE'2023-11-28') and (ledger_entries.location_id in (3,4,6,7,8,10,11,12,14,18,19,22,23,24,28,29,30,31,32,36,39,40,41,43,45,46,48,49,50,51,52,54,55,56,57,59,60,61,62,68,69,75,121)) and ((ledger_entries.`type` is null) or (ledger_entries.`type` in ('Procedure','Adjustment','AncillarySale')))) (cost=1265 rows=6236) (actual time=0.0244..4.15 rows=6192 loops=1)
-> Covering index range scan on ledger_entries using ledger_entries_location_date_type_amount_cents over (location_id = 3 AND date = '2023-11-28' AND type = NULL) OR (location_id = 3 AND date = '2023-11-28' AND type = 'Adjustment') OR (170 more) (cost=1265 rows=6236) (actual time=0.022..2.91 rows=6192 loops=1)