【问题标题】:Understanding rows_examined_per_scan and rows_produced_per_join了解 rows_examined_per_scan 和 rows_produced_per_join
【发布时间】:2021-02-11 19:50:41
【问题描述】:

我不是 mysql 人,但我最近有机会使用它 - 我必须优化 Mysql5.7 上的一些查询(我也必须在 5.6 上涵盖这一点,但从 5.7 开始显然有更多信息解释)在 AWS Aurora 上运行。这需要很多时间,涉及一些连接等。我从切割分支开始并选择仅使用两个表开始“调试”。这些不是太大(~2M 和~1.5M 行),但一般来说,我认为它们的设计不太大(varchar(255) 列上的主键等)。

问题是我想看看,所以我使用了explain format=json,并试图从中获得任何见解。

所以,让我们这么说

select cc.id, cc.col1, cc.col2, ct.col1
from my_table cc 
inner join my_table ct on ct.cc_id = cc.id

我得到了类似的东西

{
  "query_block": {
    "select_id": 1,
    "cost_info": {
      "query_cost": "5630369.56"
    },
    "nested_loop": [
      {
        "table": {
          "table_name": "cc",
          "access_type": "index",
          "possible_keys": [
            "PRIMARY"
          ],
          "key_length": "258",
          "rows_examined_per_scan": 1248725,
          "rows_produced_per_join": 1248725,
          "filtered": "100.00",
          "using_index": true,
          "cost_info": {
            "read_cost": "3732979.00",
            "eval_cost": "249745.00",
            "prefix_cost": "3982724.00",
            "data_read_per_join": "1G"
          }
        }
      },
      {
        "table": {
          "table_name": "ct",
          "access_type": "ref",
          "possible_keys": [
            "cc_id_idx"
          ],
          "key": "cc_id_idx",
          "key_length": "257",
          "ref": [
            "cc.id"
          ],
          "rows_examined_per_scan": 1,
          "rows_produced_per_join": 1373037,
          "filtered": "100.00",
          "cost_info": {
            "read_cost": "1373037.97",
            "eval_cost": "274607.59",
            "prefix_cost": "5630369.56",
            "data_read_per_join": "3G"
          },
          "used_columns": [
            "id",
            "col_1",
            "col_2",
            ...
          ]
        }
      }
    ]
  }
}

我在理解这里究竟发生了什么方面存在问题。我认为 mysql 进行嵌套连接,它从表 cc 开始,然后对于每一行它到达 ctaccess_typeref"rows_examined_per_scan": 1)。它使它 > 1M 次 ("rows_produced_per_join": 1373037)。那是对的吗?我一直在寻找任何文档,但我没有找到任何关于如何在加入的上下文中读取这些值的具体信息 - 也许只是我的 google-fu 不够强大。任何人都可以给我任何线索吗?

(当我正在寻找任何加速它的选项时,我想强制 mysql 进行哈希连接,这在 Aurora(我正在尝试 Aurora 2.09)上以/*+ HASH_JOIN(cc) */ 的形式可用,但它在任何情况下都不会影响查询计划 - 但对于另一个问题来说这是一个相当大的问题。)

【问题讨论】:

    标签: mysql query-performance mysql-5.7 explain amazon-aurora


    【解决方案1】:
    select cc.id, cc.col1, cc.col2, ct.col1
        from my_table cc 
        inner join my_table ct  on ct.cc_id = cc.id
    

    如果有WHERE 子句,可能会导致优化器选择WHERE 中提到的表作为JOIN 中的“第一个”表。

    如果没有WHERE,优化器通常会选择较小的表。作为“第一”。

    然后它通常会做一个NLJ(嵌套循环连接):

    1. 读取“第一个”表的所有行(可能被WHERE 过滤)。
    2. 对于这些行中的每一行,它都会到达另一个表 (NLJ)。

    第 2 步有时通过将整个“第二”表读入内存并构建散列来完成。但这仅适用于“小”第二张桌子。你的桌子似乎太大了。

    我提出这一切是因为您提出的查询已简化; “真实”查询的执行方式可能与 EXPLAIN 不同。

    一些观察:

    • “key_length”是“大”且不一致——检查声明,包括排序规则。不要盲目地使用VARCHAR(255) 当一些合理的长度是实用的。在JOIN 中混合排序规则对性能是致命的。 (这并不能证明查询存在这个问题。)
    • 虽然我只是批评了VARCHAR(255)PRIMARY KEY,但我不同意那些惊呼它很糟糕并且你“必须”切换到INT 的人。你能告诉我们有什么类型的数据吗? UUID vs 短字符串 vs URL vs ... -- 不同的优化技术可能适用,具体取决于数据。
    • "using_index": true -- 这意味着所有该查询中的表所需的列都在正在使用的INDEX 中找到。这对性能有好处。但是,如果您在 SELECT 子句中添加另一列,您可能会取消它。
    • "used_columns" -- 列表似乎比查询需要的长?索引中有很多列吗? EXPLAIN 是否与查询不匹配?
    • “散列”很少比“BTree”好。 (我不知道 Aurora 的具体情况,所以对于您尝试的索引提示我无话可说。)

    编写的简单查询将花费很长时间——扫描一个表(或索引),以及对另一个表的大量 BTree 查找。

    回到你原来的问题。这是我在解释中看到的:

    • “嵌套循环”== NLJ,正如预期的那样,如上所述。
    • 大成本 -- 加入大表时可以预料
    • “使用索引”== 很好,但唯一的“可能的键”是“PRIMARY”。所以,它实际上是一个表扫描,而不是真正的“索引扫描”。
    • “Key_length”——见上文
    • “Used_columns”——见上文
    • access_type 是 ref 和 "rows_examined_per_scan": 1 -- 查找(见上文)。但请注意,“rows_scanned”是一个估计值。实际值取决于cc_id 是否为UNIQUE
    • “rows_produced_per_join”——没那么重要

    此类问题请提供SHOW CREATE TABLE

    【讨论】:

    • 感谢您的回答!我很欣赏它的详细程度和你给我的建议。问题通常与排序规则和稍微不匹配的数据类型有关。
    猜你喜欢
    • 2012-02-27
    • 2014-07-28
    • 2017-09-05
    • 2011-11-11
    • 2021-07-26
    • 2019-12-10
    • 2014-11-05
    • 2020-08-07
    • 2010-12-22
    相关资源
    最近更新 更多