MySQL 5.7 与 5.6 : Index usage wrong at first, 但 "automagically"几周后修复

标签 mysql sql performance indexing relational-database

上下文:

我有一个 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”语句已成功复制。

但是几天过去了,我这边没有任何工作,错误的查询开始使用正确的索引!全部固定。没有完成任何工作。疯狂的一天。

问题:

  1. 什么会影响特定查询执行计划的选择?索引统计数据不是唯一的吗?

  2. 是否有任何“缓存/预热”的东西,超过两周的实际工作可以让优化者改变主意?

  3. “分析表”难道不足以保证两个查询具有相同的执行计划吗?

  4. 还有什么可以解释执行两周后,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 安装中都看到过这种不一致、不可预测的行为(比我希望列举的还要多)。

关于您的具体问题:

  1. 只有统计数据应该产生影响。然而,当表中存在大量索引(大约超过 10 个)时,查询计划也总是容易出现困惑。还有我上面提到的heisenbug。

  2. 否,但表统计信息始终在后台维护。听起来它最终得到了正确的数据分布估计。

  3. 应该如此,但通常并非如此。您可能需要查看以下设置:

    innodb_stats_persistent_sample_pages innodb_stats_persistent

  4. optimizer_switch 设置。您可能想尝试在 5.7 上将其设置为与 5.6 上设置的相同标志。有时它会有所帮助(但不是太频繁)。

关于MySQL 5.7 与 5.6 : Index usage wrong at first, 但 "automagically"几周后修复,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63930408/

相关文章:

php - PHP 文件中的安全性

php - Zend_DB 将大量数据导出到 csv 的正确策略是什么? – block – fetchAll – fetchRow

mysql - 嵌套查询返回值

java - PDF 的高效 SVG 渲染(Java、Batik、Flying Saucer)

python - 有没有办法进一步优化Python的heapq.nlargest来选择前N个项目?

php - 临时表与数组

php - 如何使用连接查询从两个表中删除行数据???

java - Hibernate 与附加表的多对多关系

java - 在 hibernate 中使用 native 查询进行内连接

python - 矩形阵列中距离的最快计算