前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >MySQL索引(五)索引优化分析工具

MySQL索引(五)索引优化分析工具

作者头像
鳄鱼儿
发布2024-05-21 20:46:43
870
发布2024-05-21 20:46:43
举报
文章被收录于专栏:鳄鱼儿的技术分享

Trace 工具简介

Trace 是 MySQL 5.6 版本后提供的 SQL 跟踪工具,用于了解优化器 (optimizer) 在选择执行计划时的决策过程,包括表访问方法、各种开销计算和转换等信息。

当启用 trace 工具时,可以将跟踪结果记录到 INFORMATION_SCHEMA.OPTIMIZER_TRACE 表中,支持分析以下语句:SELECT、INSERT、REPLACE、UPDATE、DELETE、EXPLAIN、SET、DECLARE、CASE、IF、RETURN、CALL。

注意:trace 功能默认关闭,启用 trace 工具会对 MySQL 性能产生影响,因此仅适用于临时分析 SQL 语句的使用,使用完毕后请立即关闭。

相关参数介绍

  • optimizer_trace
    • optimizer_trace总开关:开启或关闭 optimizer_trace,默认值为 enabled=off,one_line=off
      • enabled:是否开启 optimizer_trace,取值为 on 表示开启,off 表示关闭。
      • one_line:是否开启单行存储,取值为 on 表示开启,off 表示关闭,用 json 格式存储。单行模式可以减少存储空间。
  • optimizer_trace_features
    • 控制 optimizer_trace 跟踪的内容,默认值:greedy_search=on,range_optimizer=on,dynamic_range=on,repeated_subselect=on,表示开启所有跟踪项。
  • greedy_search:是否跟踪贪心搜索。
    • range_optimizer:是否跟踪范围优化器。
  • dynamic_range:是否跟踪动态范围优化。
    • repeated_subselect:是否跟踪子查询,如果设置为 off,只跟踪第一条 Item_subselect 的执行。
  • optimizer_trace_limit:控制展示多少条 optimizer_trace 结果,默认为 1。
  • optimizer_trace_offset:设置展示 optimizer trace 的偏移量,默认为-1,表示展示最新的一条 SQL 语句。
  • optimizer_trace_max_mem_size:定义 optimizer_trace 堆栈信息允许的最大内存,默认为 1048576。
  • end_markers_in_json:如果 JSON 结构很大,很难将右括号和左括号配对。为了提高可读性,可以设置为 on,在右括号附近添加注释,默认为 off。

注意:

  • 这些参数可以使用 set 指令进行控制。例如,要开启 trace 工具,可以使用如下指令:set optimizer_trace="enabled=on",end_markers_in_json=on;。通过 set global 也可以将其设置为全局开启,即每个会话连接都能跟踪执行的 SQL语句。
  • optimizer_trace_limitoptimizer_trace_offset 这两个参数经常一起使用。
    • 默认情况下,由于 optimizer_trace_offset=-1optimizer_trace_limit=1,只记录最近的一条 SQL 语句,并且每次只展示一条数据。
    • 若通过设置 SET optimizer_trace_offset=-2, optimizer_trace_limit=1,则可以记录倒数第二条 SQL 语句。

如何使用 trace 工具

开启trace 工具,并设置格式为JSON,设置trace的缓存大小,避免因为容量大小而不能显示完整的跟踪过程。

代码语言:javascript
复制
--- 会话级别临时开启,只在当前会话生效,关闭当前会话后失效
set session optimizer_trace="enabled=on",end_markers_in_json=on;

--- 此方式需要手动关闭,或者重启使其失效
set optimizer_trace="enabled=on";

执行需要分析的SQL语句,这里以 MySQL索引(四)常见的索引优化手段中示例表作为演示。小鱼这里分析下文中提到的几个sql 语句。

字符串范围查找

代码语言:javascript
复制
SELECT * FROM employees WHERE name < 'Li';
SELECT * FROM employees WHERE name > 'Li';

查询 information_schema.optimizer_trace,就会以表格的格式输出跟踪记录,其中我们需要查看的信息在trace 字段中。

代码语言:javascript
复制
SELECT * FROM information_schema.OPTIMIZER_TRACE;

这里小鱼将信息摘出来作为示例,在信息上进行注释补充讲解,json 格式较长,可能会影响阅读体验。

json 格式是在 trace 字段,由于小鱼这里开启了 end_markers_in_json=on ,trace 字段会以json 格式展示。

分析 SELECT * FROM employees WHERE name < 'Li'; 语句的trace 字段。

代码语言:javascript
复制
{
  "steps": [
    {
      "join_preparation": {   --- 第一阶段:sql的准备阶段,对sql进行格式化
        "select#": 1,
        "steps": [
          {
            "expanded_query": "/* select#1 */ select `employees`.`id` AS `id`,`employees`.`name` AS `name`,`employees`.`age` AS `age`,`employees`.`position` AS `position`,`employees`.`hire_time` AS `hire_time` from `employees` where (`employees`.`name` < 'Li')"
          }
        ] /* steps */
      } /* join_preparation */
    },
    {
      "join_optimization": {   --- 第二阶段:sql的优化阶段
        "select#": 1,
        "steps": [
          {
            "condition_processing": {   --- 条件处理
              "condition": "WHERE",
              "original_condition": "(`employees`.`name` < 'Li')",
              "steps": [
                {
                  "transformation": "equality_propagation",
                  "resulting_condition": "(`employees`.`name` < 'Li')"
                },
                {
                  "transformation": "constant_propagation",
                  "resulting_condition": "(`employees`.`name` < 'Li')"
                },
                {
                  "transformation": "trivial_condition_removal",
                  "resulting_condition": "(`employees`.`name` < 'Li')"
                }
              ] /* steps */
            } /* condition_processing */
          },
          {
            "substitute_generated_columns": {
            } /* substitute_generated_columns */
          },
          {
            "table_dependencies": [   --- 表的依赖情况
              {
                "table": "`employees`",
                "row_may_be_null": false,
                "map_bit": 0,
                "depends_on_map_bits": [
                ] /* depends_on_map_bits */
              }
            ] /* table_dependencies */
          },
          {
            "ref_optimizer_key_uses": [
            ] /* ref_optimizer_key_uses */
          },
          {
            "rows_estimation": [   --- 这里预估了表访问的成本
              {
                "table": "`employees`",
                "range_analysis": {
                  "table_scan": {     --- 全表扫描成本
                    "rows": 100067,   --- 扫描行数
                    "cost": 20304     --- 扫描成本
                  } /* table_scan */,
                  "potential_range_indexes": [  --- 查询可能会使用到的索引
                    {
                      "index": "PRIMARY",       --- 主键索引
                      "usable": false,
                      "cause": "not_applicable"
                    },
                    {
                      "index": "idx_name_age_position",   --- 二级索引
                      "usable": true,
                      "key_parts": [
                        "name",
                        "age",
                        "position",
                        "id"
                      ] /* key_parts */
                    }
                  ] /* potential_range_indexes */,
                  "setup_range_conditions": [
                  ] /* setup_range_conditions */,
                  "group_index_range": {
                    "chosen": false,
                    "cause": "not_group_by_or_distinct"
                  } /* group_index_range */,
                  "analyzing_range_alternatives": {   --- 分析各个索引使用成本
                    "range_scan_alternatives": [
                      {
                        "index": "idx_name_age_position",
                        "ranges": [
                          "name < Li"                 --- 索引的使用范围
                        ] /* ranges */,
                        "index_dives_for_eq_ranges": true,
                        "rowid_ordered": false,       ---使用该索引获取的记录是否按照主键排序
                        "using_mrr": false, 
                        "index_only": false,          --- 是否使用覆盖索引
                        "rows": 50033,                --- 索引扫描行数
                        "cost": 60041,                --- 索引使用成本
                        "chosen": false,              --- 是否选择该索引
                        "cause": "cost"
                      }
                    ] /* range_scan_alternatives */,
                    "analyzing_roworder_intersect": {
                      "usable": false,
                      "cause": "too_few_roworder_scans"
                    } /* analyzing_roworder_intersect */
                  } /* analyzing_range_alternatives */
                } /* range_analysis */
              }
            ] /* rows_estimation */
          },
          {
            "considered_execution_plans": [
              {
                "plan_prefix": [
                ] /* plan_prefix */,
                "table": "`employees`",
                "best_access_path": {            --- 最优访问路径
                  "considered_access_paths": [   --- 最终选择访问路径
                    {
                      "rows_to_scan": 100067,
                      "access_type": "scan",     --- 访问类型(scan:全表扫描)  
                      "resulting_rows": 100067,
                      "cost": 20302,
                      "chosen": true             --- 确认选择
                    }
                  ] /* considered_access_paths */
                } /* best_access_path */,
                "condition_filtering_pct": 100,
                "rows_for_plan": 100067,
                "cost_for_plan": 20302,
                "chosen": true
              }
            ] /* considered_execution_plans */
          },
          {
            "attaching_conditions_to_tables": {
              "original_condition": "(`employees`.`name` < 'Li')",
              "attached_conditions_computation": [
              ] /* attached_conditions_computation */,
              "attached_conditions_summary": [
                {
                  "table": "`employees`",
                  "attached": "(`employees`.`name` < 'Li')"
                }
              ] /* attached_conditions_summary */
            } /* attaching_conditions_to_tables */
          },
          {
            "refine_plan": [
              {
                "table": "`employees`"
              }
            ] /* refine_plan */
          }
        ] /* steps */
      } /* join_optimization */
    },
    {
      "join_execution": {   --- 第三阶段:sql的执行阶段
        "select#": 1,
        "steps": [
        ] /* steps */
      } /* join_execution */
    }
  ] /* steps */
}

在执行一下sql 语句 SELECT * FROM employees WHERE name > 'Li';,同样再查询一下 information_schema.OPTIMIZER_TRACE 信息。

代码语言:javascript
复制
SELECT * FROM employees WHERE name > 'Li';
SELECT * FROM information_schema.OPTIMIZER_TRACE;

由于json 数据较长,此部分不全部展示了。部分trace 字段的摘要如下:

代码语言:javascript
复制
{
  "steps": [
    {
      "join_optimization": {
        "steps": [
          {
            "rows_estimation": [ --- 这里预估了表访问的成本
              {
                  "analyzing_range_alternatives": {--分析各个索引使用成本
                    "range_scan_alternatives": [
                      {
                        "index": "idx_name_age_position",
                        "ranges": [
                          "Li < name"
                        ] /* ranges */,
                        "index_dives_for_eq_ranges": true,
                        "rowid_ordered": false,
                        "using_mrr": false,
                        "index_only": false,    --- 是否使用覆盖索引
                        "rows": 2,              --- 索引扫描行数
                        "cost": 3.41,           --- 索引使用成本
                        "chosen": true          --- 是否选择该索引
                      }
                    ] 
                  } /* analyzing_range_alternatives */,
                } /* range_analysis */
              }
            ] /* rows_estimation */
          },
          {
            "considered_execution_plans": [
              {
                "plan_prefix": [
                ] /* plan_prefix */,
                "table": "`employees`",
                "best_access_path": {
                  "considered_access_paths": [
                    {
                      "rows_to_scan": 2,
                      "access_type": "range",   ---访问类型(range:索引范围扫描)
                      "range_details": {
                        "used_index": "idx_name_age_position"
                      } /* range_details */,
                      "resulting_rows": 2,
                      "cost": 3.81,
                      "chosen": true
                    }
                  ] /* considered_access_paths */
                } /* best_access_path */,
                "condition_filtering_pct": 100,
                "rows_for_plan": 2,
                "cost_for_plan": 3.81,
                "chosen": true
              }
            ] /* considered_execution_plans */
          },
        ] /* steps */
      } /* join_optimization */
    },
  ] /* steps */
}

用trace 工具对比执行这两个sql 语句,我们发现,第一个sql 语句中全表扫描的成本低于索引扫描,mysql最终选择全表扫描,而在第二个语句中索引扫描的成本低于全表扫描,mysql最终选择索引扫描。

关闭trace 工具

代码语言:javascript
复制
set session optimizer_trace="enabled=off";

参考

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-05-21,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Trace 工具简介
    • 相关参数介绍
      • 如何使用 trace 工具
      • 参考
      相关产品与服务
      云数据库 MySQL
      腾讯云数据库 MySQL(TencentDB for MySQL)为用户提供安全可靠,性能卓越、易于维护的企业级云数据库服务。其具备6大企业级特性,包括企业级定制内核、企业级高可用、企业级高可靠、企业级安全、企业级扩展以及企业级智能运维。通过使用腾讯云数据库 MySQL,可实现分钟级别的数据库部署、弹性扩展以及全自动化的运维管理,不仅经济实惠,而且稳定可靠,易于运维。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档