Context:
I have a MySQL 5.6 as master, that have two replicas, one of them is also a MySQL 5.6 instance and other is a MySQL 5.7. Heavy queries are distributed evenly across the two replicas.
The 5.6 Replica is up and running for about 2 months, the 5.7 is newer, running about two weeks.
Using AWS RDS.
Weird Behaviour:
Just after the creation, I noticed that some queries were much slower at 5.7 because of “wrong index usage”, as shown by comparing the EXPLAIN result on both versions. Even some queries that had an USE INDEX clause, seemed to have no effect on 5.7 database.
Giving that it was a fresh new replica, I thought: “Maybe index statistics aren’t up to date?“.
Not the case. I have a routine that runs “analyse table” for every table on the system overnight at the master DB, so index statistics should be fine. I can confirm that the “analyse table” statement is successfully being replicated by checking the last_update on the replicas mysql.innodb_index_stats.
But some days have passed by, and without any work on my side, the bad queries started to use the right indexes! All fixed. No work done. Crazy day.
Questions:
What could affect the choice for a specific query execution plan? Index statistics aren’t the only thing?
Is there any “caching/warming up” stuff, that over two weeks of actual work could make The Optimiser change his mind?
Shouldn’t “analyse table” be enough to guarantee that both queries would have the same execution plan?
What else can explain that after two weeks of executions, the MySQL 5.7 starts to work same way as MySQL 5.6?
Remember, both databases were REPLICAS from the SAME SOURCE that receive EVENLY DISTRIBUTED traffic.
SQL Example:
The following query (this is an obfuscated version), was using the BAD index, and one day later it was using the GOOD index. Just like Magic™.
SELECT sale.number,
seller.id,
seller.name,
sale.id,
COALESCE(billed_amount, 0),
COALESCE(billing.tax_id, '---'),
billing.date,
CASE COALESCE(sale.seller_name_2, '') WHEN '' THEN sale.seller_name_1 ELSE sale.seller_name_2 END,
sale.business_id,
sale.business_name,
sale.total
FROM app_billing billing
JOIN app_sale sale ON billing.sale_id = sale.id
JOIN app_seller seller ON seller.id = sale.seller_id
WHERE billing.tenant_id = 515
AND billing.removed = FALSE
AND billing.date BETWEEN '2020-08-01' AND '2020-08-31'
AND sale.status = 2
AND sale.seller_id IN (368);
-- MySQL 5.7 (BAD, really bad estimate and counter intuitive decision [should definitely be a range scan])
-- 1st step: There are only one seller with ID 368, so thats right.
-- 2nd step: 692 is pretty accurate, there are 695 sales for the seller ID 368.
-- 3rd step: This is tricky. There are a total of 270776 billing records that matches the 695 sale_ids from the previous step. 65% of the matches, have only one correspondence, but the remaining 35% have between 1000 and 5000 correspondences. Average would be 1360.
-- Estimated total number of processed records: 942000~.
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
| :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- |
| 1 | SIMPLE | seller | NULL | const | PRIMARY | PRIMARY | 4 | const | 1 | 100 | NULL |
| 1 | SIMPLE | sale | NULL | ref | PRIMARY,idx_seller_id | idx_seller_id | 4 | const | 692 | 10 | Using where |
| 1 | SIMPLE | billing | NULL | ref | idx_sale_id,idx_tenant_id_removed_date | idx_sale_id | 4 | sale.id | 2 | 2.16 | Using where |
-- MySQL 5.6 (GOOD, using range scan)
-- 1st step: There are only one seller with ID 368, so thats right.
-- 2nd step: 421 is almost perfect. There are actually 422 billing records across the date '2020-08-01' AND '2020-08-31' for the tenant_id 515 that arent removed.
-- 3rd step: That's right. There's only one sale correspondence per billing,
-- Estimated total number of processed records: 421.
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
| :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- |
| 1 | SIMPLE | seller | const | PRIMARY | PRIMARY | 4 | const | 1 | NULL |
| 1 | SIMPLE | billing | range | idx_sale_id,idx_tenant_id_removed_date | idx_tenant_id_removed_date | 9 | NULL | 421 | Using index condition |
| 1 | SIMPLE | sale | eq_ref | PRIMARY,idx_seller_id | PRIMARY | 4 | billing.sale_id | 1 | Using where |