上下文:
我有一个 MySQL 5.6 作为主实例,有两个副本,其中一个也是 MySQL 5.6 实例,另一个是 MySQL 5.7。大量查询均匀分布在两个副本中。
5.6 副本已启动并运行约 2 个月,5.7 较新,运行约两周。
使用 AWS RDS。
奇怪的行为:
创建后不久,我注意到由于“索引使用错误”,一些查询在 5.7 中速度慢得多,如比较两个版本上的 EXPLAIN
结果所示。即使某些具有 USE INDEX 子句的查询似乎对 5.7 数据库也没有影响。
考虑到这是一个全新的副本,我想:“也许索引统计信息不是最新的?”。
事实并非如此。我有一个例程,可以在主数据库上过夜对系统上的每个表运行“分析表”,因此索引统计数据应该没问题。我可以通过检查副本 mysql.innodb_index_stats
上的 last_update
来确认“analysis table”语句已成功复制。
但是几天过去了,我这边没有任何工作,错误的查询开始使用正确的索引!全部固定。没有完成任何工作。疯狂的一天。
问题:
什么会影响特定查询执行计划的选择?索引统计数据不是唯一的吗?
是否有任何“缓存/预热”的东西,超过两周的实际工作可以让优化者改变主意?
“分析表”难道不足以保证两个查询具有相同的执行计划吗?
还有什么可以解释执行两周后,MySQL 5.7 开始以与 MySQL 5.6 相同的方式工作?
请记住,这两个数据库都是来自同一来源的副本,并且接收均匀分布的流量。
SQL 示例:
以下查询(这是一个模糊版本)使用的是 BAD 索引,一天后它使用的是 GOOD 索引。就像 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 |
最佳答案
MySQL 5.7 及更高版本受到查询优化器 heisenbug 的严重影响,这使得它无法预测地选择一个极其糟糕的执行计划。听起来你好像犯规了。我在迄今为止所见过的每个 5.7 安装中都看到过这种不一致、不可预测的行为(比我希望列举的还要多)。
关于您的具体问题:
只有统计数据应该产生影响。然而,当表中存在大量索引(大约超过 10 个)时,查询计划也总是容易出现困惑。还有我上面提到的heisenbug。
否,但表统计信息始终在后台维护。听起来它最终得到了正确的数据分布估计。
应该如此,但通常并非如此。您可能需要查看以下设置:
innodb_stats_persistent_sample_pages innodb_stats_persistent
optimizer_switch 设置。您可能想尝试在 5.7 上将其设置为与 5.6 上设置的相同标志。有时它会有所帮助(但不是太频繁)。
关于MySQL 5.7 与 5.6 : Index usage wrong at first, 但 "automagically"几周后修复,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63930408/