【SQL性能提升篇🔥】MySQL常用优化工具Explain、Trace工具、优化案例

MySQL常用优化工具解析

Explain执行计划

explain是最简单的sql执行分析工具,可以很方便的查看是否使用到了索引,使用到了哪个索引。数据查询的方式等信息。

explain各个列的意思

可能的值 作用
id 1、2、3、4... 查询的id顺序号,查询的执行顺序
select_type simple、primary、subquery、derived 这条查询的复杂类型
table tableA、tableB 查询的表
type system > const > eq_ref > ref > range > index > ALL 查询的类型
possible_keys index_name 可能使用到的索引列
key index_name 实际上使用到的索引列
key_len number 索引的长度
ref const、查询的字段 查找时用到的字段
rows 1331 预估扫描行数
filtered 80.0 使用索引过滤的数据占总行数的百分比,越高说明索引利用率越高
Extra Using index、UsingWhere、filesort... 查询额外的信息

Explain扩展计划

在MySQL8.0之前,使用explain extended进行SQL查询的分析,会多返回一列filtered,用于显示使用索引扫描数据的利用率。

并且如果SQL中存在一些问题的话,MySQL可能会给你一些警告⚠️,可以通过这些警告来帮助找到需要优化的点。

这个警告就说明了,你的SQL在执行的过程中存在隐式转换。不能利用IX_unionid索引进行查找。

sql 复制代码
# unionid字段为varchar类型,查询时指定的是int类型。
select *
from table_a a
where a.unionid = 1

Trace工具

sql 复制代码
# 查看是否开启trace记录
show variables like 'optimizer_trace'
# 开启trace记录
SET OPTIMIZER_TRACE="enabled=on",END_MARKERS_IN_JSON=on;
# 记录多少条
SET optimizer_trace_offset=-30, optimizer_trace_limit=30;
# 要执行的SQL
SELECT* FROM employees a WHERE a.insert_time>now() limit 100;
# 查询trace信息
SELECT * FROM INFORMATION_SCHEMA.OPTIMIZER_TRACE where QUERY like '%employees%';
# 关闭
SET optimizer_trace="enabled=off";

trace返回的是一个超大的json包,但是可以分为三大部分。

正则替换所有的注释/* 注释 */ 正则: /\*.*\*/

  • join_preparation 准备阶段,预检完成后会将sql进行一个格式化
  • join_optimization 优化阶段,也是分析Trace的重点。
  • join_execution 执行阶段

join_optimization步骤详解

condition_processing

主要对条件进行优化处理,也就是WHERE、HAVING后边的那一段

  1. condition 优化的类型,WHERE、HAVING

  2. original_condition 优化前的原始语句

  3. steps 优化的步骤,是个数组,主要分为3个步骤

    1. quality_propagation 等值条件转换

    2. constant_propagation 常量语句转换

    3. trivial_condition_removal 无效条件移除

    json 复制代码
      {
        "condition_processing": {
          "condition": "WHERE",
          "original_condition": "((`a`.`insert_time` = '2022-09-05 14:01:47') and (`a`.`emp_name` = '39107935-2ce0-11ed-96c6-0242ac150003'))",
          "steps": [
            {
              "transformation": "equality_propagation",
              "resulting_condition": "((`a`.`emp_name` = '39107935-2ce0-11ed-96c6-0242ac150003') and multiple equal('2022-09-05 14:01:47', `a`.`insert_time`))"
            },
            {
              "transformation": "constant_propagation",
              "resulting_condition": "((`a`.`emp_name` = '39107935-2ce0-11ed-96c6-0242ac150003') and multiple equal('2022-09-05 14:01:47', `a`.`insert_time`))"
            },
            {
              "transformation": "trivial_condition_removal",
              "resulting_condition": "((`a`.`emp_name` = '39107935-2ce0-11ed-96c6-0242ac150003') and multiple equal(TIMESTAMP'2022-09-05 14:01:47', `a`.`insert_time`))"
            }
          ] /* steps */
        } /* condition_processing */
      }

substitute_generated_columns

用于替换虚拟生成列

json 复制代码
 {
   "substitute_generated_columns": {
   } /* substitute_generated_columns */
 }

table_dependencies

查询使用到的表依赖关系

json 复制代码
{
  "table_dependencies": [
    {
      "table": "`employees` `a`",
      "row_may_be_null": false,
      "map_bit": 0,
      "depends_on_map_bits": [
      ] /* depends_on_map_bits */
    }
  ] /* table_dependencies */
}
  1. table 用到的表和别名

  2. row_may_be_null:行是否可能为NULL,这里是指JOIN操作之后,这张表里的数据是不是可能为NULL。如果语句中使用了LEFT JOIN,则后一张表的row_may_be_null会显示为true

  3. map_bit:表的映射编号,从0开始递增

  4. depends_on_map_bits:依赖的映射表。主要是当使用STRAIGHT_JOIN强行控制连接顺序或者LEFT JOIN/RIGHT JOIN有顺序差别时,会在depends_on_map_bits中展示前置表的map_bit值。

ref_optimizer_key_uses

列出所有可能引用到的索引,数组,会列出多个索引

json 复制代码
{
  "ref_optimizer_key_uses": [
    {
      "table": "`employees` `a`",
      "field": "emp_name",
      "equals": "'39107935-2ce0-11ed-96c6-0242ac150003'",
      "null_rejecting": true
    },
    {
      "table": "`employees` `a`",
      "field": "emp_name",
      "equals": "'39107935-2ce0-11ed-96c6-0242ac150003'",
      "null_rejecting": true
    }
  ] /* ref_optimizer_key_uses */
}
  1. table 表名

  2. field 索引列名

  3. equals 条件

  4. null_rejecting 是不是null

rows_estimation

用于估算扫描的行数,查询的代价

  1. table 查询的表

  2. range_analysis 范围分析

    1. table_scan 如果全表扫描的话,可能需要扫描多少行

      1. row 估算的扫描行
      2. cost 估算的扫描时间
    2. potential_range_indexes 表中所有的索引,并分析是否可用

      1. index 索引名称
      2. usable 是否使用
      3. cause 是否适用
      4. key_parts 索引列名称,联合索引会有这个数组的返回
    3. setup_range_conditions 如果有可下推的条件,则带条件考虑范围查询

    4. group_index_range 当使用了GROUP BY或DISTINCT时,是否有合适的索引

      1. choose 是否选择使用这个索引
      2. cause 使用/不使用的原因
    5. skip_scan_range (MySQL8.0后的新特性,8.0以下没有这个)

      1. 是否使用跳跃式索引,表中有一个联合索引的时候,查询要满足最左前缀法则,否则不会命中索引,但是在8.0以后,MySQL会根据第一个索引列的distinct后的数量,来估算是否使用跳跃式索引,如果满足条件,就算不满足最左前缀法则,也可以命中索引。

    6. analyzing_range_alternatives 分析range查询索引使用的成本/代价

      1. range_scan_alternatives 范围扫描分析
        1. index:索引名
        2. ranges:range扫描的条件范围
        3. index_dives_for_eq_ranges:是否使用了index dive,该值会被参数eq_range_index_dive_limit变量值影响。
        4. rowid_ordered:该range扫描的结果集是否根据PK值进行排序
        5. using_mrr:是否使用了mrr
        6. index_only:表示是否使用了覆盖索引
        7. rows:扫描的行数
        8. cost:索引的使用成本
        9. chosen:表示是否使用了该索引
    7. analyzing_roworder_intersect 分析是否使用了索引合并,如果未使用,则会给出cause

    8. chosen_range_access_summary 分析以上步骤的执行结果,确认最终结果

      1. range_access_plan 最终确认的扫描计划
        1. type 扫描的类型
        2. index 使用的索引
        3. row 预估扫描的行数
        4. range 扫描的条件范围
      2. rows_for_plan 该执行计划扫描的行数
      3. cost_for_plan 该执行计划扫描的代价
      4. chosen 是否选择这个扫描计划

considered_execution_plans

对比各个扫描计划的开销,选择相对最优的执行计划

  1. plan_prefix 当前计划的前置执行计划。
  2. table 表名,别名
  3. best_access_path 最优的访问路径
    1. access_type 访问类型 对应explain的type
    2. index 索引名称
    3. row 扫描的行数
    4. cost 代价
    5. chosen 是否选择这个索引
  4. condition_filtering_pct 估算扫描行数在总行数中的占比
  5. rows_for_plan 最终的扫描行数
  6. cost_for_plan 最终的扫描代价
  7. chosen 是否使用这个扫描计划

attaching_conditions_to_tables

改造原有的WHRER条件,并适当增加附加条件,便于单表的数据查询

  1. original_condition 原始的条件SQL语句
  2. attached_conditions_computation 附加条件计算,如果当前的索引类型是ref,则会去计算range是否优于ref,是则考虑替换ref查询
  3. attached_conditions_summary 最终决定新增的附加条件SQL

finalizing_table_conditions

最终的查询条件

  1. table 表
  2. original_table_condition 原始查询条件
  3. final_table_condition 最终查询条件

附录

完整的Trace

json 复制代码
{
  "steps": [
    {
      "join_preparation": {
        "select#": 1,
        "steps": [
          {
            "expanded_query": "/* select#1 */ select `a`.`emp_id` AS `emp_id`,`a`.`emp_name` AS `emp_name`,`a`.`emp_age` AS `emp_age`,`a`.`emp_mobile` AS `emp_mobile`,`a`.`group_name` AS `group_name`,`a`.`insert_time` AS `insert_time` from `employees` `a` where ((`a`.`insert_time` = '2022-09-05 14:01:47') and (`a`.`emp_name` = '3a13715b-2ce0-11ed-96c6-0242ac150003'))"
          }
        ] /* steps */
      } /* join_preparation */
    },
    {
      "join_optimization": {
        "select#": 1,
        "steps": [
          {
            "condition_processing": {
              "condition": "WHERE",
              "original_condition": "((`a`.`insert_time` = '2022-09-05 14:01:47') and (`a`.`emp_name` = '3a13715b-2ce0-11ed-96c6-0242ac150003'))",
              "steps": [
                {
                  "transformation": "equality_propagation",
                  "resulting_condition": "((`a`.`emp_name` = '3a13715b-2ce0-11ed-96c6-0242ac150003') and multiple equal('2022-09-05 14:01:47', `a`.`insert_time`))"
                },
                {
                  "transformation": "constant_propagation",
                  "resulting_condition": "((`a`.`emp_name` = '3a13715b-2ce0-11ed-96c6-0242ac150003') and multiple equal('2022-09-05 14:01:47', `a`.`insert_time`))"
                },
                {
                  "transformation": "trivial_condition_removal",
                  "resulting_condition": "((`a`.`emp_name` = '3a13715b-2ce0-11ed-96c6-0242ac150003') and multiple equal(TIMESTAMP'2022-09-05 14:01:47', `a`.`insert_time`))"
                }
              ] /* steps */
            } /* condition_processing */
          },
          {
            "substitute_generated_columns": {
            } /* substitute_generated_columns */
          },
          {
            "table_dependencies": [
              {
                "table": "`employees` `a`",
                "row_may_be_null": false,
                "map_bit": 0,
                "depends_on_map_bits": [
                ] /* depends_on_map_bits */
              }
            ] /* table_dependencies */
          },
          {
            "ref_optimizer_key_uses": [
              {
                "table": "`employees` `a`",
                "field": "emp_name",
                "equals": "'3a13715b-2ce0-11ed-96c6-0242ac150003'",
                "null_rejecting": true
              },
              {
                "table": "`employees` `a`",
                "field": "emp_name",
                "equals": "'3a13715b-2ce0-11ed-96c6-0242ac150003'",
                "null_rejecting": true
              }
            ] /* ref_optimizer_key_uses */
          },
          {
            "rows_estimation": [
              {
                "table": "`employees` `a`",
                "range_analysis": {
                  "table_scan": {
                    "rows": 10959575,
                    "cost": 1.13869e+07
                  } /* table_scan */,
                  "potential_range_indexes": [
                    {
                      "index": "PRIMARY",
                      "usable": false,
                      "cause": "not_applicable"
                    },
                    {
                      "index": "idx_name",
                      "usable": false,
                      "cause": "not_applicable"
                    },
                    {
                      "index": "idx_emp_name",
                      "usable": true,
                      "key_parts": [
                        "emp_name",
                        "emp_id"
                      ] /* key_parts */
                    },
                    {
                      "index": "idx_ename_gname_insert",
                      "usable": true,
                      "key_parts": [
                        "emp_name",
                        "group_name",
                        "insert_time",
                        "emp_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 */,
                  "skip_scan_range": {
                    "potential_skip_scan_indexes": [
                      {
                        "index": "idx_emp_name",
                        "usable": false,
                        "cause": "query_references_nonkey_column"
                      },
                      {
                        "index": "idx_ename_gname_insert",
                        "usable": false,
                        "cause": "query_references_nonkey_column"
                      }
                    ] /* potential_skip_scan_indexes */
                  } /* skip_scan_range */,
                  "analyzing_range_alternatives": {
                    "range_scan_alternatives": [
                      {
                        "index": "idx_emp_name",
                        "ranges": [
                          "3a13715b-2ce0-11ed-96c6-0242ac150003 <= emp_name <= 3a13715b-2ce0-11ed-96c6-0242ac150003"
                        ] /* ranges */,
                        "index_dives_for_eq_ranges": true,
                        "rowid_ordered": true,
                        "using_mrr": false,
                        "index_only": false,
                        "rows": 1,
                        "cost": 1.98798,
                        "chosen": true
                      },
                      {
                        "index": "idx_ename_gname_insert",
                        "ranges": [
                          "3a13715b-2ce0-11ed-96c6-0242ac150003 <= emp_name <= 3a13715b-2ce0-11ed-96c6-0242ac150003"
                        ] /* ranges */,
                        "index_dives_for_eq_ranges": true,
                        "rowid_ordered": false,
                        "using_mrr": false,
                        "index_only": false,
                        "rows": 1,
                        "cost": 1.98798,
                        "chosen": false,
                        "cause": "cost"
                      }
                    ] /* range_scan_alternatives */,
                    "analyzing_roworder_intersect": {
                      "usable": false,
                      "cause": "too_few_roworder_scans"
                    } /* analyzing_roworder_intersect */
                  } /* analyzing_range_alternatives */,
                  "chosen_range_access_summary": {
                    "range_access_plan": {
                      "type": "range_scan",
                      "index": "idx_emp_name",
                      "rows": 1,
                      "ranges": [
                        "3a13715b-2ce0-11ed-96c6-0242ac150003 <= emp_name <= 3a13715b-2ce0-11ed-96c6-0242ac150003"
                      ] /* ranges */
                    } /* range_access_plan */,
                    "rows_for_plan": 1,
                    "cost_for_plan": 1.98798,
                    "chosen": true
                  } /* chosen_range_access_summary */
                } /* range_analysis */
              }
            ] /* rows_estimation */
          },
          {
            "considered_execution_plans": [
              {
                "plan_prefix": [
                ] /* plan_prefix */,
                "table": "`employees` `a`",
                "best_access_path": {
                  "considered_access_paths": [
                    {
                      "access_type": "ref",
                      "index": "idx_emp_name",
                      "rows": 1,
                      "cost": 1.03899,
                      "chosen": true
                    },
                    {
                      "access_type": "ref",
                      "index": "idx_ename_gname_insert",
                      "rows": 1,
                      "cost": 1.03899,
                      "chosen": false
                    },
                    {
                      "access_type": "range",
                      "range_details": {
                        "used_index": "idx_emp_name"
                      } /* range_details */,
                      "chosen": false,
                      "cause": "heuristic_index_cheaper"
                    }
                  ] /* considered_access_paths */
                } /* best_access_path */,
                "condition_filtering_pct": 100,
                "rows_for_plan": 1,
                "cost_for_plan": 1.03899,
                "chosen": true
              }
            ] /* considered_execution_plans */
          },
          {
            "attaching_conditions_to_tables": {
              "original_condition": "((`a`.`insert_time` = TIMESTAMP'2022-09-05 14:01:47') and (`a`.`emp_name` = '3a13715b-2ce0-11ed-96c6-0242ac150003'))",
              "attached_conditions_computation": [
              ] /* attached_conditions_computation */,
              "attached_conditions_summary": [
                {
                  "table": "`employees` `a`",
                  "attached": "((`a`.`insert_time` = TIMESTAMP'2022-09-05 14:01:47') and (`a`.`emp_name` = '3a13715b-2ce0-11ed-96c6-0242ac150003'))"
                }
              ] /* attached_conditions_summary */
            } /* attaching_conditions_to_tables */
          },
          {
            "finalizing_table_conditions": [
              {
                "table": "`employees` `a`",
                "original_table_condition": "((`a`.`insert_time` = TIMESTAMP'2022-09-05 14:01:47') and (`a`.`emp_name` = '3a13715b-2ce0-11ed-96c6-0242ac150003'))",
                "final_table_condition   ": "(`a`.`insert_time` = TIMESTAMP'2022-09-05 14:01:47')"
              }
            ] /* finalizing_table_conditions */
          },
          {
            "refine_plan": [
              {
                "table": "`employees` `a`"
              }
            ] /* refine_plan */
          }
        ] /* steps */
      } /* join_optimization */
    },
    {
      "join_execution": {
        "select#": 1,
        "steps": [
        ] /* steps */
      } /* join_execution */
    }
  ] /* steps */
}

Trace解读

掘金


MySQL优化方法论

可以将优化看作4层,分别对应以下层次。

层级越靠上(SQL层 > MySQL层 > InnoDB层 > 硬件层),那么优化的效率就越快,通常也会在第一层(SQL)进行优化。

方法一:小表驱动大表

也可以理解为是小数据集驱动大数据集的查询。

例如一个场景,表A和表B。

  • table_a表为商品表,存放所有商品的信息,
  • table_b表为商品统计表,也可以理解为是一个结果表,存放了商品的销量信息,以JSON格式存储,JSON内包含这个商品一年的销量数据。

业务一:查询商品列表(商品表),查询商品的图片(商品图片表),查询购买人数(订单表),根据销量进行排序。

假设啊,不要较真订单数据和图片数据为什么不在商品表里。为什么不提前算好。

正常的写法可能是这样的:

sql 复制代码
select 
	a.name, # 商品名称
	a.id, # 商品id
	cast(json_value(b.sale_json,'$.data.12') AS SIGNED) as sale_num # 使用json函数获取2023年12月的销量,并转换为int类型
from table_a as a
left join table_b b on a.id = b.id and b.sale_year=2023 # 关联一下,并只要2023年的销量数据
order by sale_num desc
limit 10,100

为了不让第一条sql变的更加复杂,查询商品图片和购买人数可能会拆分成剩下的两个sql进行。

sql 复制代码
# 查询图片
select image,goods_id from goods_imgs where goods_id in (1,2,3,4,5)
sql 复制代码
# 查询购买人数
select count(distinct user_id),goods_id
from orders
where goods_id in(1,2,3,4,5)
group by user_id,goods_id

那对应我们的程序可能就是这样的:

java 复制代码
// 查询商品
List<Goods> goodsList = goodsService.queryGoodsList(params...);
// 查询图片
List<Image> imageList = imageService.queryImageByGoodsId(goodsIdList);
// 查询购买人数
List<BuyerDTO> buyInfoList = imageService.queryImageByGoodsId(goodsIdList);

// 拼装数据...省略

仔细查看会发现,我们的3条sql语句,其实只有查图片,查购买人数真正用到的小结果集驱动大结果集,而查询商品的sql是没有的,这个业务的性能瓶颈极大可能就是在第一条查询商品的sql上。

所以我们能不能改一种写法,最好都能用上小表驱动大表的思想。

改进sql格式,优化查询方式

思考一下,第一个sql怎么样才能优化,第一个sql的核心是要根据销量进行排序,我们能不能把销量的顺序先确定下来,然后获取商品id,然后在使用商品id作为条件去查询商品列表,是不是就可以小表驱动大表了。

相关推荐
他日若遂凌云志2 小时前
深入剖析 Fantasy 框架的消息设计与序列化机制:协同架构下的高效转换与场景适配
后端
快手技术2 小时前
快手Klear-Reasoner登顶8B模型榜首,GPPO算法双效强化稳定性与探索能力!
后端
二闹2 小时前
三个注解,到底该用哪一个?别再傻傻分不清了!
后端
用户49055816081252 小时前
当控制面更新一条 ACL 规则时,如何更新给数据面
后端
林太白2 小时前
Nuxt.js搭建一个官网如何简单
前端·javascript·后端
码事漫谈2 小时前
VS Code 终端完全指南
后端
该用户已不存在3 小时前
OpenJDK、Temurin、GraalVM...到底该装哪个?
java·后端
怀刃3 小时前
内存监控对应解决方案
后端
码事漫谈3 小时前
VS Code Copilot 内联聊天与提示词技巧指南
后端
Moonbit3 小时前
MoonBit Perals Vol.06: MoonBit 与 LLVM 共舞 (上):编译前端实现
后端·算法·编程语言