mysql的trace追踪SQL工具,进行sql优化

trace是MySQL5.6版本后提供的SQL跟踪工具,通过使用trace可以让我们明白optimizer(优化器)如何选择执行计划。

**注意:**开启trace工具会影响mysql性能,所以只适合临时分析sql使用,用完之后请立即关闭。

测试数据脚本:

bash 复制代码
DROP TABLE IF EXISTS `t_student`;
CREATE TABLE `t_student` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `std_name` varchar(30) NOT NULL,
  `age` tinyint(3) unsigned NOT NULL,
  `class_id` int(11) unsigned NOT NULL,
  `gmt_create` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  KEY `idx_std_age` (`age`),
  KEY `idx_std_name_age_class` (`std_name`,`age`,`class_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=100766 DEFAULT CHARSET=utf8mb4;
 
--添加测试数据的存储过程

DROP PROCEDURE IF EXISTS proc_insert_student; 
DELIMITER $$
CREATE PROCEDURE proc_insert_student()        
BEGIN
  DECLARE i INT;                   
  SET i=1;                          
  WHILE i<=100000 DO                 
    INSERT INTO t_student(std_name,age,class_id) VALUES(CONCAT('Li Lei',i), (i MOD 120)+1 ,(i MOD 3)+1);   
    SET i=i+1;                       
  END WHILE;
END $$
 
-- 执行存储过程
call proc_insert_student();

trace工具用法

一.查看trace开关状态,默认关闭的

bash 复制代码
show variables like 'optimizer_trace';

二.开启face

1.会话级别临时开启,只在当前会话生效。

bash 复制代码
set session optimizer_trace="enabled=on",end_markers_in_json=on;

2.永久开启(重启失效)

注意用完关闭

bash 复制代码
set optimizer_trace="enabled=on";

三.用法

1.在查询sql后加上固定sql,例:

bash 复制代码
set session optimizer_trace="enabled=on",end_markers_in_json=on;  
select * from t_student where std_name > 'a' order by age;
SELECT * FROM information_schema.OPTIMIZER_TRACE;

2.查看执行计划,并未使用到联合索引,联合索引name使用的范围查询,一般都不会用到索引。 如果用name索引需要遍历name字段联合索引树,然后还需要根据遍历出来的主键值去主键索引树里再去查出最终数据,成本比全表扫描还高 。

bash 复制代码
EXPLAIN SELECT * FROM t_student WHERE std_name > 'a' ORDER BY age;
bash 复制代码
EXPLAIN SELECT std_name,age,class_id FROM t_student WHERE std_name > 'a' ORDER BY age;

3。如果查询是联合索引字段,那就使用了覆盖索引,这样的name范围查询,联合索引才会使用到,这样只需要遍历name字段的联合索引树就能拿到所有结果,叶子节点只存放二级索引的数据,这就不用回表操作。

TRACE列的json数据拷贝出来查看。主要是看cost值,成本

bash 复制代码
SELECT * FROM information_schema.OPTIMIZER_TRACE;
bash 复制代码
{
  "steps": [
    {
      "join_preparation": {  -- 第一阶段:SQL准备阶段,格式化sql
        "select#": 1,
        "steps": [
          {
            "expanded_query": "/* select#1 */ select `t_student`.`id` AS `id`,`t_student`.`std_name` AS `std_name`,`t_student`.`age` AS `age`,`t_student`.`class_id` AS `class_id`,`t_student`.`gmt_create` AS `gmt_create` from `t_student` where (`t_student`.`std_name` > 'a') order by `t_student`.`age`"
          }
        ] /* steps */
      } /* join_preparation */
    },
    {
      "join_optimization": {  -- 第二阶段:SQL优化阶段
        "select#": 1,
        "steps": [
          {
            "condition_processing": {  -- 条件处理
              "condition": "WHERE",
              "original_condition": "(`t_student`.`std_name` > 'a')",
              "steps": [
                {
                  "transformation": "equality_propagation",
                  "resulting_condition": "(`t_student`.`std_name` > 'a')"
                },
                {
                  "transformation": "constant_propagation",
                  "resulting_condition": "(`t_student`.`std_name` > 'a')"
                },
                {
                  "transformation": "trivial_condition_removal",
                  "resulting_condition": "(`t_student`.`std_name` > 'a')"
                }
              ] /* steps */
            } /* condition_processing */
          },
          {
            "substitute_generated_columns": {
            } /* substitute_generated_columns */
          },
          {
            "table_dependencies": [  -- 表依赖详情
              {
                "table": "`t_student`",
                "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": "`t_student`",
                "range_analysis": {
                  "table_scan": {   -- 全表扫描
                    "rows": 100300,  -- 行数
                    "cost": 20351  -- 查询消耗
                  } /* table_scan */,
                  "potential_range_indexes": [  -- 查询可能使用的索引
                    {
                      "index": "PRIMARY",  -- 主键索引
                      "usable": false,  -- 未使用
                      "cause": "not_applicable"  -- 原因:不适合
                    },
                    {
                      "index": "idx_std_age",  -- age索引
                      "usable": false,  -- 未使用
                      "cause": "not_applicable"  -- 原因:不适合
                    },
                    {
                      "index": "idx_std_name_age_class",  -- stdname,age,class的组合索引
                      "usable": true,  -- 使用
                      "key_parts": [
                        "std_name",
                        "age",
                        "class_id",
                        "id"
                      ] /* key_parts */
                    }
                  ] /* potential_range_indexes */,
                  "setup_range_conditions": [
                  ] /* setup_range_conditions */,
                  "group_index_range": {  -- group 用到的索引
                    "chosen": false,  -- 未使用
                    "cause": "not_group_by_or_distinct"  -- 原因:未使用group by 或者 distinct
                  } /* group_index_range */,
                  "analyzing_range_alternatives": {   -- 分析各个索引使用成本
                    "range_scan_alternatives": [
                      {
                        "index": "idx_std_name_age_class",
                        "ranges": [
                          "a < std_name"  -- 索引使用范围
                        ] /* ranges */,
                        "index_dives_for_eq_ranges": true,
                        "rowid_ordered": false,  -- 使用该索引获取的记录是否按照主键排序
                        "using_mrr": false,
                        "index_only": false,  -- 是否使用覆盖索引
                        "rows": 50150,  -- 索引扫描行数
                        "cost": 60181,   -- 索引使用成本
                        "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": "`t_student`",
                "best_access_path": {  -- 最优访问路径
                  "considered_access_paths": [  --分析出的最终访问路径
                    {
                      "rows_to_scan": 100300,
                      "access_type": "scan",  -- 访问类型:为scan,全表扫描
                      "resulting_rows": 100300,
                      "cost": 20349,
                      "chosen": true,  -- 确定选择
                      "use_tmp_table": true
                    }
                  ] /* considered_access_paths */
                } /* best_access_path */,
                "condition_filtering_pct": 100,
                "rows_for_plan": 100300,
                "cost_for_plan": 20349,
                "sort_cost": 100300,
                "new_cost_for_plan": 120649,
                "chosen": true
              }
            ] /* considered_execution_plans */
          },
          {
            "attaching_conditions_to_tables": {   -- 为查询的表添加条件
              "original_condition": "(`t_student`.`std_name` > 'a')",
              "attached_conditions_computation": [
              ] /* attached_conditions_computation */,
              "attached_conditions_summary": [    -- 添加条件结果
                {
                  "table": "`t_student`",
                  "attached": "(`t_student`.`std_name` > 'a')"
                }
              ] /* attached_conditions_summary */
            } /* attaching_conditions_to_tables */
          },
          {
            "clause_processing": {   -- order by 处理
              "clause": "ORDER BY",
              "original_clause": "`t_student`.`age`",
              "items": [
                {
                  "item": "`t_student`.`age`"
                }
              ] /* items */,
              "resulting_clause_is_simple": true,
              "resulting_clause": "`t_student`.`age`"
            } /* clause_processing */
          },
          {
            "reconsidering_access_paths_for_index_ordering": {    -- 重构索引处理顺序
              "clause": "ORDER BY",
              "steps": [
              ] /* steps */,
              "index_order_summary": {
                "table": "`t_student`",
                "index_provides_order": false,
                "order_direction": "undefined",
                "index": "unknown",
                "plan_changed": false
              } /* index_order_summary */
            } /* reconsidering_access_paths_for_index_ordering */
          },
          {
            "refine_plan": [
              {
                "table": "`t_student`"
              }
            ] /* refine_plan */
          }
        ] /* steps */
      } /* join_optimization */
    },
    {
      "join_execution": {    -- 第三阶段:SQL执行阶段
        "select#": 1,
        "steps": [
          {
            "filesort_information": [
              {
                "direction": "asc",
                "table": "`t_student`",
                "field": "age"
              }
            ] /* filesort_information */,
            "filesort_priority_queue_optimization": {
              "usable": false,
              "cause": "not applicable (no LIMIT)"
            } /* filesort_priority_queue_optimization */,
            "filesort_execution": [
            ] /* filesort_execution */,
            "filesort_summary": {
              "rows": 100000,
              "examined_rows": 100000,
              "number_of_tmp_files": 14,
              "sort_buffer_size": 262016,
              "sort_mode": "<sort_key, packed_additional_fields>"
            } /* filesort_summary */
          }
        ] /* steps */
      } /* join_execution */
    }
  ] /* steps */
}

MySQL认为 全表扫描的成本低于索引扫描,所以mysql最终选择全表扫描,

4.关闭trace

bash 复制代码
 set session optimizer_trace="enabled=off";    
相关推荐
吾日三省吾码1 小时前
JVM 性能调优
java
弗拉唐2 小时前
springBoot,mp,ssm整合案例
java·spring boot·mybatis
oi772 小时前
使用itextpdf进行pdf模版填充中文文本时部分字不显示问题
java·服务器
mqiqe2 小时前
Python MySQL通过Binlog 获取变更记录 恢复数据
开发语言·python·mysql
工业甲酰苯胺2 小时前
MySQL 主从复制之多线程复制
android·mysql·adb
BestandW1shEs2 小时前
谈谈Mysql的常见基础问题
数据库·mysql
重生之Java开发工程师3 小时前
MySQL中的CAST类型转换函数
数据库·sql·mysql
教练、我想打篮球3 小时前
66 mysql 的 表自增长锁
数据库·mysql
Ljw...3 小时前
表的操作(MySQL)
数据库·mysql·表的操作