trace是MySQL5.6版本后提供的SQL跟踪工具,通过使用trace可以让我们明白optimizer(优化器)如何选择执行计划。
注意:开启trace工具会影响mysql性能,所以只适合临时分析sql使用,用完之后请立即关闭。
测试数据脚本:
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();
show variables like 'optimizer_trace';
1.会话级别临时开启,只在当前会话生效。
set session optimizer_trace="enabled=on",end_markers_in_json=on;
2.永久开启(重启失效)
注意用完关闭
set optimizer_trace="enabled=on";
1.在查询sql后加上固定sql,例:
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字段联合索引树,然后还需要根据遍历出来的主键值去主键索引树里再去查出最终数据,成本比全表扫描还高 。
EXPLAIN SELECT * FROM t_student WHERE std_name > 'a' ORDER BY age;
EXPLAIN SELECT std_name,age,class_id FROM t_student WHERE std_name > 'a' ORDER BY age;
3。如果查询是联合索引字段,那就使用了覆盖索引,这样的name范围查询,联合索引才会使用到,这样只需要遍历name字段的联合索引树就能拿到所有结果,叶子节点只存放二级索引的数据,这就不用回表操作。
TRACE列的json数据拷贝出来查看。主要是看cost值,成本
SELECT * FROM information_schema.OPTIMIZER_TRACE;
{ "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": "" } /* filesort_summary */ } ] /* steps */ } /* join_execution */ } ] /* steps */ }
MySQL认为 全表扫描的成本低于索引扫描,所以mysql最终选择全表扫描,
4.关闭trace
set session optimizer_trace="enabled=off";