This is the last update to this very LONG post.
Problem Summary:
In a database containing a field for datetime and a field for milliseconds (and indexes for both), MySQL picks an index of milliseconds when I specify a query containing datetime, and it chooses the datetime index when I query on milliseconds. This happens across 6 independently built systems that have never been interconnected.
My Solution:
So to sum up what I'm going with for a solution--I tried everything suggested here and nothing actually causes MySQl to consistently "Pick" the correct index, but it will obey a hint. It seems that MySQL just has an unfixable bug in index choice and all I can do is suggest what index to use manually, so I'll pass a hint in from Hibernate.
It's not even like it's an optimizer issue--It's choosing an index with the entirely wrong data type!
I'll leave the question open for a while and see if I get any better answers, if not I'll post "Just tell MySQL which index to use because index selection is broken" as an answer.
The original post (with edits, very long):
I have a strange problem with MySQL (v. 8.00.37 GPL version using InnoDB).
I have a "Log" database. It includes the fields:
"level" (varchar (225)),
"timestamp" (datetime)
"timestamp_in_millis" (bigint)
We have created two indexes for this table, they are:
(level, timestamp)
(level, timestamp_in_millis).
Although other fields can be selected, for the testing I've done I've reduced it to these three fields and these two indexes only.
So the basic problem is that when I issue:
select * from log where level='INFO' and timestamp > "2024-07-10 23:59:00
it takes a very long time to return.
After taking a day to evaluate it and learning a LOT more about MySQL and indexing in the process I found that "explain" for that line tells me it's choosing the index including the "timestamp_in_millis" key.
Funny thing is that the line:
"explain select * from log where level='info' and timestamp_in_millis > '17198355046'
(A number I pulled that should be about the same as the other date) it says it's using the index with the "timestamp" and getting the same bad performance.
if I FORCE INDEX to the correct index, it fixes both queries (although strangely it says type is "range" when I would expect "INDEX" since I'm using the exact same parameters as in the index). This isn't an option for long-term though since we are using hibernate and I'm not sure hibernate will pass that through for us.
The other thing that makes it work is replacing level = 'INFO' with level LIKE 'INFO'. It will then choose to use the correct index and come back in a reasonable time. I figured this out after reading that ICP will pass down equals but not like, however disabling ICP didn't help.
Also note:
- This cross-index behavior is completely reliable on all 6 environments that exhibit it after dozens of EXPLAIN statements on each.
- All of the mysql servers are independent--we don't back up from one and restore to another or anything like that.
This same problem is repeatable on all dev, test, staging and production servers except one (I can't find anything unique about the one that works!). That's a total of 7 servers I've been testing, 5 that exhibit this behavior repeatedly, 100% of the time.
Things I've tried:
- rebuilding indexes on my dev server. This once fixed it but then it broke when I did it again
- rebuilding indexes on our test server. No effect, but takes to long to do repeatedly.
- ANALYZE TABLE
- OPTIMIZE TABLE
- dropping all indexes and rebuilding them
- Disabling Index Condition Pushdown
- Using "Like" instead of "=" which did cause it to use the correct index partially.
Any hints as to more things I could try or a reason this might be happening?
show indexes from log looks like this:
The other query showed a very skewed distribution with most days having just a few entries at most levels but INFO has ~20K/day. Since our searches are typically level=ERROR that should be a good index I think.
Another bit of insanity I just discovered: the selected index is changing based on what columns are to be returned.
"explain select * from log where timestamp_in_millis > 1719878355046"
incorrectly selects the timestamp (datetime) index, however
explain select timestamp_in_millis from log where timestamp_in_millis > 1719878355046
selects the correct (Int) index. As I've come to expect, the reverse is true as well, where "select * from log where timestamp..." chooses the timestamp_in_millis, "select timestamp from log where..." chooses the correct timestamp index.