了解 rows_examined_per_scan 和 rows_produced_per_join

Understanding rows_examined_per_scan and rows_produced_per_join

我不是 mysql 人,但我最近有机会使用它 - 我必须优化 Mysql5.7 上的一些查询(我必须介绍一下同样在 5.6 上,但从 5.7 开始,因为它在解释中显然有更多信息) 运行 on AWS Aurora。这需要很多时间,涉及一些连接等。我从切割分支开始,选择仅用两个 table 开始 'debugging'。这些不是太大(~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 在以 table cc 开始的地方进行嵌套连接,然后对于它命中的每一行 ctaccess_typeref"rows_examined_per_scan": 1)。它使它> 1M次("rows_produced_per_join": 1373037)。那是对的吗?我正在为此寻找任何文档,但我没有找到任何关于如何在加入上下文中读取这些值的具体信息 - 也许只是我的 google-fu 不够强大。有没有人能给我一些线索?

(因为我正在寻找加速它的任何选项,所以我想强制 mysql 进行散列连接,这在 Aurora 上可用(我在 Aurora 2.09 上尝试过)形式为/*+ HASH_JOIN(cc) */,但它在任何情况下都不会影响查询计划 - 但它是另一个问题的问题。)

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 中提到的 table 作为“第一个" table 在 JOIN.

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

然后它通常 执行 NLJ(嵌套循环连接):

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

第 2 步 有时 通过将整个“第二个”table 读入内存并构建哈希来完成。但这只适用于“小”秒 table。您的 table 似乎太大了。

我提出所有这些是因为您提出的问题得到了简化; 'real' 查询可能不会像 EXPLAIN.

那样执行

一些观察:

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

所写的简单查询将花费很长时间 -- 扫描一个 table(或索引),再加上对另一个 table.[=28 的大量 BTree 查找=]

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

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

如有此类问题,请提供SHOW CREATE TABLE