准备工作:
mysql 版本5.7.31
CREATE TABLE `collect` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`title` varchar(50) DEFAULT NULL,
`vtype` int(11) NOT NULL,
PRIMARY KEY (`id`),
KEY `collect_index` (`id`,`title`) USING BTREE,
KEY `test_index` (`title`,`vtype`),
KEY `fk` (`vtype`),
CONSTRAINT `fk` FOREIGN KEY (`vtype`) REFERENCES `vtype_info` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1795476 DEFAULT CHARSET=utf8;
CREATE TABLE `vtype_info` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8;
初始化数据
INSERT INTO `test`.`vtype_info`(`id`, `name`) VALUES (1, 'q');
INSERT INTO `test`.`vtype_info`(`id`, `name`) VALUES (2, 'w');
INSERT INTO `test`.`vtype_info`(`id`, `name`) VALUES (3, 'e');
INSERT INTO `test`.`vtype_info`(`id`, `name`) VALUES (4, 'r');
INSERT INTO `test`.`vtype_info`(`id`, `name`) VALUES (5, 't');
INSERT INTO `test`.`vtype_info`(`id`, `name`) VALUES (6, 'y');
INSERT INTO `test`.`vtype_info`(`id`, `name`) VALUES (7, 'u');
INSERT INTO `test`.`vtype_info`(`id`, `name`) VALUES (8, 'i');
INSERT INTO `test`.`vtype_info`(`id`, `name`) VALUES (9, 'o');
INSERT INTO `test`.`vtype_info`(`id`, `name`) VALUES (10, 'p');
INSERT INTO `test`.`vtype_info`(`id`, `name`) VALUES (11, 'a');
CREATE DEFINER=`root`@`localhost` PROCEDURE `test`()
BEGIN
DECLARE n int DEFAULT 1;
WHILE n < 1000000 DO
insert into collect (title,vtype) value (CEILING(RAND()*10),CEILING(RAND()*10));
set n = n + 1;
END WHILE;
END
mysql开启optimizer_trace:
mysql -hlocalhost -uroot -p
mysql> SET OPTIMIZER_TRACE="enabled=on",END_MARKERS_IN_JSON=on; # be readable
mysql> SET OPTIMIZER_TRACE_MAX_MEM_SIZE=1000000; # avoid small default
mysql> select title,vtype from collect c ,vtype_info v where c.vtype=v.id and c.vtype=1 and c.title='1' limit 10;
mysql> SELECT * FROM INFORMATION_SCHEMA.OPTIMIZER_TRACE;
查看trace细节:
{
"steps": [
{
"join_preparation": {
"select#": 1,
"steps": [
{
"expanded_query": "/* select#1 */ select `c`.`title` AS `title`,`c`.`vtype` AS `vtype` from `collect` `c` join `vtype_info` `v` where ((`c`.`vtype` = `v`.`id`) and (`c`.`vtype` = 1) and (`c`.`title` = '1')) limit 10"
}
] /* steps */
} /* join_preparation */
},
准备工作 对查询分解标号 如果内查询会有 “select#”: 1、 “select#”: 2 … 这个标号后面会用到
下面是具体优化过程
{
"join_optimization": {
"select#": 1,
"steps": [
{
"condition_processing": {
"condition": "WHERE",
"original_condition": "((`c`.`vtype` = `v`.`id`) and (`c`.`vtype` = 1) and (`c`.`title` = '1'))",
"steps": [
{
"transformation": "equality_propagation",
"resulting_condition": "((`c`.`title` = '1') and multiple equal(1, `c`.`vtype`, `v`.`id`))"
},
{
"transformation": "constant_propagation",
"resulting_condition": "((`c`.`title` = '1') and multiple equal(1, `c`.`vtype`, `v`.`id`))"
},
{
"transformation": "trivial_condition_removal",
"resulting_condition": "((`c`.`title` = '1') and multiple equal(1, `c`.`vtype`, `v`.`id`))"
}
] /* steps */
} /* condition_processing */
where 条件优化:
注意前面索引
KEY `test_index` (`title`,`vtype`),
所以可以看出mysql 5.7能够根据索引字段的顺序调整查询条件顺序
},
{
"substitute_generated_columns": {
} /* substitute_generated_columns */
},
{
"table_dependencies": [
{
"table": "`collect` `c`",
"row_may_be_null": false,
"map_bit": 0,
"depends_on_map_bits": [
] /* depends_on_map_bits */
},
{
"table": "`vtype_info` `v`",
"row_may_be_null": false,
"map_bit": 1,
"depends_on_map_bits": [
] /* depends_on_map_bits */
}
] /* table_dependencies */
},
{
"ref_optimizer_key_uses": [
{
"table": "`collect` `c`",
"field": "title",
"equals": "'1'",
"null_rejecting": false
},
{
"table": "`collect` `c`",
"field": "vtype",
"equals": "1",
"null_rejecting": false
},
{
"table": "`collect` `c`",
"field": "vtype",
"equals": "1",
"null_rejecting": false
},
{
"table": "`vtype_info` `v`",
"field": "id",
"equals": "1",
"null_rejecting": false
}
] /* ref_optimizer_key_uses */
},
ref_optimizer_key_uses 外键索引判断
注意: null_rejecting表示判断外键字段是不是会因为包含null值而拒绝使用
{
"rows_estimation": [ //计算各种方式查询的效率
{
"table": "`collect` `c`",
"range_analysis": {
"table_scan": {
"rows": 998811,
"cost": 1.2e6
} /* table_scan 全表扫描 */,
"potential_range_indexes": [
{
"index": "PRIMARY", //主键
"usable": false, //不能使用
"cause": "not_applicable" //原因:不合适
},
{
"index": "collect_index",
"usable": false,
"cause": "not_applicable"
},
{
"index": "test_index",
"usable": true, //找到合适的索引
"key_parts": [
"title",
"vtype",
"id"
] /* key_parts */
},
test_index是创建的覆盖索引
可以看到该索引覆盖的字段包含title,vtype,id 这也验证了联合索引中包含主键,如果从索引中取不到全部的查询字段会通过主键会表
{
"index": "fk",
"usable": true,
"key_parts": [
"vtype",
"id"
] /* key_parts */
}
] /* potential_range_indexes */,
"best_covering_index_scan": { //最合适的索引扫描
"index": "test_index",
"cost": 219348,
"chosen": true
} /* best_covering_index_scan */,
"setup_range_conditions": [
] /* setup_range_conditions */,
"group_index_range": {
"chosen": false,
"cause": "not_single_table"
} /* group_index_range */,
"analyzing_range_alternatives": {
"range_scan_alternatives": [
{
"index": "test_index",
"ranges": [
"1 <= title <= 1 AND 1 <= vtype <= 1"
] /* ranges */,
"index_dives_for_eq_ranges": true,
"rowid_ordered": true,
"using_mrr": false,
"index_only": true,
"rows": 18080,
"cost": 3971.5,
"chosen": true
},
{
"index": "fk",
"ranges": [
"1 <= vtype <= 1"
] /* ranges */,
"index_dives_for_eq_ranges": true,
"rowid_ordered": true,
"using_mrr": false,
"index_only": false,
"rows": 201670,
"cost": 242005,
"chosen": false,
"cause": "cost"
}
] /* range_scan_alternatives */,
"analyzing_roworder_intersect": {
"intersecting_indexes": [
{
"index": "test_index",
"index_scan_cost": 355.49,
"cumulated_index_scan_cost": 355.49,
"disk_sweep_cost": 0,
"cumulated_total_cost": 355.49,
"usable": true,
"matching_rows_now": 18080,
"isect_covering_with_this_index": true,
"chosen": true
}
] /* intersecting_indexes */,
"clustered_pk": {
"clustered_pk_added_to_intersect": false,
"cause": "no_clustered_pk_index"
} /* clustered_pk */,
"chosen": false,
"cause": "too_few_indexes_to_merge"
} /* analyzing_roworder_intersect */
} /* analyzing_range_alternatives */,
"chosen_range_access_summary": {
"range_access_plan": {
"type": "range_scan",
"index": "test_index",
"rows": 18080,
"ranges": [
"1 <= title <= 1 AND 1 <= vtype <= 1"
] /* ranges */
} /* range_access_plan */,
"rows_for_plan": 18080,
"cost_for_plan": 3971.5,
"chosen": true
} /* chosen_range_access_summary */
} /* range_analysis */
},
{
"table": "`vtype_info` `v`",
"rows": 1,
"cost": 1,
"table_type": "const",
"empty": false
}
] /* rows_estimation */
},
{
"considered_execution_plans": [
{
"plan_prefix": [
"`vtype_info` `v`" //关联查询的主表
] /* plan_prefix */,
"table": "`collect` `c`",
"best_access_path": {
"considered_access_paths": [
{
"access_type": "ref",
"index": "test_index",
"rows": 18080,
"cost": 3971.5,
"chosen": true
},
{
"access_type": "ref",
"index": "fk",
"rows": 201670,
"cost": 46202,
"chosen": false
},
{
"access_type": "range",
"range_details": {
"used_index": "test_index"
} /* range_details */,
"chosen": false,
"cause": "heuristic_index_cheaper"
}
] /* considered_access_paths */
} /* best_access_path */,
"condition_filtering_pct": 100,
"rows_for_plan": 18080,
"cost_for_plan": 3971.5,
"chosen": true
}
] /* considered_execution_plans */
},
considered_execution_plans 是联表顺序优化
因为关联查询首先会根据条件筛选主表,然后再loop主表的数据查询出关联表的数据,如果是left join、right join 主表将是固定的,但是inner join mysql会选择出最优的主表,如果在使用inner join不想托管mysql优化这一过程可以使用STRAIGHT_JOIN
{
"attaching_conditions_to_tables": {
"original_condition": "((`c`.`vtype` = 1) and (`c`.`title` = '1'))",
"attached_conditions_computation": [
] /* attached_conditions_computation */,
"attached_conditions_summary": [
{
"table": "`collect` `c`",
"attached": null
}
] /* attached_conditions_summary */
} /* attaching_conditions_to_tables */
},
{
"refine_plan": [
{
"table": "`collect` `c`"
}
] /* refine_plan */
}
] /* steps */
} /* join_optimization */
},
{
"join_execution": {
"select#": 1,
"steps": [
] /* steps */
} /* join_execution */
}
] /* steps */
}
参考 https://dev.mysql.com/doc/internals/en/tracing-example.html