计划对比 - 非官方 MySQL 8.0 优化指南 - 学习笔记

优化器的职责是在大量可能的执行计划中挑选最快的一个。在这些选择中可能有几种不同的索引及对应的访问方法。目前我们已经演示了 索引p 和两种不同的执行计划:

  1. 人口列的范围扫描
  2. 全表扫描

演示中索引p(人口) 的选择性不高,现在我们增加一个索引c(大洲)。在典型的生产环境中,你可能想去掉索引p,因为它没有提供价值,但是我们可以看看优化器是怎么评估多种选择的。

例子7:多个索引的选择

ALTER TABLE Country ADD INDEX c (continent);

EXPLAIN FORMAT=JSON
SELECT * FROM Country WHERE population > 5000000 continent='Asia';
{
  "query_block": {
     "select_id": 1,
     "cost_info": {
     "query_cost": "28.20"
     },
     "table": {
     "table_name": "Country",
     "access_type": "ref",           # 访问方式 ref 说明用了
     "possible_keys": [              # 不具唯一性的索引
     "p",
     "c"
     ],
     "key": "c",                     # 选择了大洲的索引
     "used_key_parts": [
     "Continent"
     ],
     "key_length": "1",
     "ref": [
     "const"
     ],
     "rows_examined_per_scan": 51,  # 该计划需要扫描的行数
     "rows_produced_per_join": 23,
     "filtered": "45.19",
     "cost_info": {
     "read_cost": "18.00",
     "eval_cost": "4.61",
     "prefix_cost": "28.20",
     "data_read_per_join": "5K"
     },
     "used_columns": [
     ...
     ],
     "attached_condition": "(`world`.`Country`.`Population` > 5000000)"
     }
  }
}

在例子7中,我们可以看到选择索引c(大洲)优于 索引p(人口)和全表扫描。它履行了索引的功能——减少工作量。优化器评估在使用这个索引后,只需要扫描 51 行。另一种理解是,有足够多的国家不在亚洲,因此大洲索引是很有选择性的。

在例子8中,我们稍微调整查询语句,把条件人口数增加到 5 亿,索引的选择就会变成索引p(人口)。这是很合理的:
只有两个国家拥有多于 5 亿的人口,因此人口索引变得很有选择性。

例子8:使用更大的人口数条件,优化器选择人口索引

EXPLAIN FORMAT=JSON
SELECT * FROM Country WHERE continent='Asia' and population > 500000000;
{
  "query_block": {
    "select_id": 1,
    "cost_info": {
      "query_cost": "7.01"
    },
    "table": {
      "table_name": "Country",
      "access_type": "range",         # 访问方式是范围而不是 ref(对照)
      "possible_keys": [
        "p",
        "c"
      ],
      "key": "p",                     # 变为了选择人口索引
      "used_key_parts": [
        "Population"
      ],
      "key_length": "4",
      "rows_examined_per_scan": 2,    # 只需要扫描 2 行
      "rows_produced_per_join": 0,
      "filtered": "21.34",
      "index_condition": "(`world`.`Country`.`Population` > 500000000)",
      "cost_info": {
        "read_cost": "6.58",
        "eval_cost": "0.43",
        "prefix_cost": "7.01",
        "data_read_per_join": "112"
      },
      "used_columns": [
        ...
      ],
      "attached_condition": "(`world`.`Country`.`Continent` = 'Asia')"
    }
  }
}

上面的rows_examined_per_scan可以看出索引的选择性。另外有两个数据可以说明 例子7 和 例子8 索引选择不同的原因:

  1. key_length。大洲的数据类型是 1字节 的枚举,人口的是 4字节 的整型。这说明在选择性相当时,一个较短的键更优,因为每页能存放的键更多了,同样的内存就能装入更多。
  2. access_type。其他条件相当时,ref 的查询代价比 range 低。

在有这两个索引的情况下,我们的查询有至少 4 种可能的执行计划了:

  1. 人口索引的范围扫描
  2. 全表扫描
  3. 大洲索引的对照扫描
  4. 大洲索引的范围扫描(’Asia’ <= Continent <= ‘Asia’)

除了这些执行计划,通过索引合并我们还能一次用上两个索引。
在例子9 OPTIMIZER_TRACE 的输出中,我们可以看到主键索引的范围扫描也被评估过,然后被否决了。

例子9:优化器输出中,人口索引和大洲索引的对比

SET optimizer_trace="enabled=on";

SELECT * FROM Country WHERE continent='Asia' AND population > 500000000;
SELECT * FROM information_schema.optimizer_trace;
...
  "potential_range_indexes": [
    {
      "index": "PRIMARY",
      "usable": false,      # 主键上用范围扫描,不可行
      "cause": "not_applicable"
    },
    {
      "index": "p",
      "usable": true,       # 人口索引上用范围扫描,可行
      "key_parts": [ 
        "Population",   
        "Code"
      ]
    },
    {
      "index": "c",         # 大洲索引上用范围扫描,可行
      "usable": true,  
      "key_parts": [    
        "Continent",    
        "Code"
      ]
    }
  ],

...

  "analyzing_roworder_intersect": {
      "usable": false,                 # 合并索引,被否决
      "cause": "too_few_roworder_scans"
    }
  },
  "chosen_range_access_summary": {
    "range_access_plan": {
      "type": "range_scan",          # 范围扫描中,优化器偏好大洲索引
      "index": "p",
      "rows": 2,
      "ranges": [
        "500000000 < Population"
      ]
    },
    "rows_for_plan": 2,
    "cost_for_plan": 5.01,
    "chosen": true
  }

...

  {
    "considered_execution_plans": [
      {
        "plan_prefix": [
        ],
        "table": "`Country`",          
        "best_access_path": {          # 这是可行访问策略的概要
          "considered_access_paths": [
            {
              "access_type": "ref",    # 大洲索引的对照访问 
              "index": "c",
              "rows": 51,
              "cost": 69,
              "chosen": true
            },
            {
              "rows_to_scan": 2,
              "access_type": "range",  # 人口索引的范围访问
              "range_details": {
                "used_index": "p"
              },
              "resulting_rows": 2,
              "cost": 7.01,
              "chosen": true
            }
          ]
        },
        "condition_filtering_pct": 100,
        "rows_for_plan": 2,
        "cost_for_plan": 7.01,
        "chosen": true
      }
    ]
  },
...

译自:
Comparing Plans – The Unofficial MySQL 8.0 Optimizer Guide

    原文作者:mokou591
    原文地址: https://www.jianshu.com/p/a6fc94b25b42
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞