1

sql执行方案计算

mysql底层会对sql进行查询优化,依据各个方案所产生的cost成本计算最优执行方案,sql的最终执行方案是否走了索引,抑或为什么没有走索引的原因可以用trace工具来分析。
开启trace:

mysql> set session optimizer_trace="enabled=on",end_markers_in_json=on; ‐‐开启trace
mysql> select * from user where name > 'a' order by age;
mysql> SELECT * FROM information_schema.OPTIMIZER_TRACE;

查看trace字段:


6 {
7 "steps": [
8 {
9 "join_preparation": { ‐‐第一阶段:SQL准备阶段,格式化sql
10 "select#": 1,
11 "steps": [
12 {
13 "expanded_query": "/* select#1 */ select `employees`.`id` AS `id`,`employees`.`name` AS `name`,`empl
oyees`.`age` AS `age`,`employees`.`position` AS `position`,`employees`.`hire_time` AS `hire_time` from
`employees` where (`employees`.`name` > 'a') order by `employees`.`position`"
14 }
15 ] /* steps */
16 } /* join_preparation */
17 },
18 {
19 "join_optimization": { ‐‐第二阶段:SQL优化阶段
20 "select#": 1,
21 "steps": [
22 {
23 "condition_processing": { ‐‐条件处理
24 "condition": "WHERE",
25 "original_condition": "(`employees`.`name` > 'a')",
26 "steps": [
27 {
28 "transformation": "equality_propagation",
29 "resulting_condition": "(`employees`.`name` > 'a')"
30 },
31 {
32 "transformation": "constant_propagation",
33 "resulting_condition": "(`employees`.`name` > 'a')"
34 },
35 {
36 "transformation": "trivial_condition_removal",
37 "resulting_condition": "(`employees`.`name` > 'a')"
38 }
39 ] /* steps */
40 } /* condition_processing */
41 },
42 {
43 "substitute_generated_columns": {
44 } /* substitute_generated_columns */
45 },
46 {
47 "table_dependencies": [ ‐‐表依赖详情
48 {
49 "table": "`employees`",
50 "row_may_be_null": false,
51 "map_bit": 0,
52 "depends_on_map_bits": [
53 ] /* depends_on_map_bits */
54 }
55 ] /* table_dependencies */
56 },
57 {
58 "ref_optimizer_key_uses": [
59 ] /* ref_optimizer_key_uses */
60 },
61 {
62 "rows_estimation": [ ‐‐预估表的访问成本
63 {
64 "table": "`employees`",
65 "range_analysis": {
66 "table_scan": { ‐‐全表扫描情况
67 "rows": 10123, ‐‐扫描行数
68 "cost": 2054.7 ‐‐查询成本
69 } /* table_scan */,
70 "potential_range_indexes": [ ‐‐查询可能使用的索引
71 {
72 "index": "PRIMARY", ‐‐主键索引
73 "usable": false,
74 "cause": "not_applicable"
75 },
76 {
77 "index": "idx_name_age_position", ‐‐辅助索引
78 "usable": true,
79 "key_parts": [
80 "name",
81 "age",
82 "position",
83 "id"
84 ] /* key_parts */
85 }
86 ] /* potential_range_indexes */,
87 "setup_range_conditions": [
88 ] /* setup_range_conditions */,
89 "group_index_range": {
90 "chosen": false,
91 "cause": "not_group_by_or_distinct"
92 } /* group_index_range */,
93 "analyzing_range_alternatives": { ‐‐分析各个索引使用成本
94 "range_scan_alternatives": [
95 {
96 "index": "idx_name_age_position",
97 "ranges": [
98 "a < name" ‐‐索引使用范围
99 ] /* ranges */,
100 "index_dives_for_eq_ranges": true,
101 "rowid_ordered": false, ‐‐使用该索引获取的记录是否按照主键排序
102 "using_mrr": false,
103 "index_only": false, ‐‐是否使用覆盖索引
104 "rows": 5061, ‐‐索引扫描行数
105 "cost": 6074.2, ‐‐索引使用成本
106 "chosen": false, ‐‐是否选择该索引
107 "cause": "cost"
108 }
109 ] /* range_scan_alternatives */,
110 "analyzing_roworder_intersect": {
111 "usable": false,
112 "cause": "too_few_roworder_scans"
113 } /* analyzing_roworder_intersect */
114 } /* analyzing_range_alternatives */
115 } /* range_analysis */
116 }
117 ] /* rows_estimation */
118 },
119 {
120 "considered_execution_plans": [
121 {
122 "plan_prefix": [
123 ] /* plan_prefix */,
124 "table": "`employees`",
125 "best_access_path": { ‐‐最优访问路径
126 "considered_access_paths": [ ‐‐最终选择的访问路径
127 {
128 "rows_to_scan": 10123,
129 "access_type": "scan", ‐‐访问类型:为scan,全表扫描
130 "resulting_rows": 10123,
131 "cost": 2052.6,
132 "chosen": true, ‐‐确定选择
133 "use_tmp_table": true
134 }
135 ] /* considered_access_paths */
136 } /* best_access_path */,
137 "condition_filtering_pct": 100,
138 "rows_for_plan": 10123,
139 "cost_for_plan": 2052.6,
140 "sort_cost": 10123,
141 "new_cost_for_plan": 12176,
142 "chosen": true
143 }
144 ] /* considered_execution_plans */
145 },
146 {
147 "attaching_conditions_to_tables": {
148 "original_condition": "(`employees`.`name` > 'a')",
149 "attached_conditions_computation": [
150 ] /* attached_conditions_computation */,
151 "attached_conditions_summary": [
152 {
153 "table": "`employees`",
154 "attached": "(`employees`.`name` > 'a')"
155 }
156 ] /* attached_conditions_summary */
157 } /* attaching_conditions_to_tables */
158 },
159 {
160 "clause_processing": {
161 "clause": "ORDER BY",
162 "original_clause": "`employees`.`position`",
163 "items": [
164 {
165 "item": "`employees`.`position`"
166 }
167 ] /* items */,
168 "resulting_clause_is_simple": true,
169 "resulting_clause": "`employees`.`position`"
170 } /* clause_processing */
171 },
172 {
173 "reconsidering_access_paths_for_index_ordering": {
174 "clause": "ORDER BY",
175 "steps": [
176 ] /* steps */,
177 "index_order_summary": {
178 "table": "`employees`",
179 "index_provides_order": false,
180 "order_direction": "undefined",
181 "index": "unknown",
182 "plan_changed": false
183 } /* index_order_summary */
184 } /* reconsidering_access_paths_for_index_ordering */
185 },
186 {
187 "refine_plan": [
188 {
189 "table": "`employees`"
190 }
191 ] /* refine_plan */
192 }
193 ] /* steps */
194 } /* join_optimization */
195 },
196 {
197 "join_execution": { ‐‐第三阶段:SQL执行阶段
198 "select#": 1,
199 "steps": [
200 ] /* steps */
201 } /* join_execution */
202 }
203 ] /* steps */
204 }
205
206 结论:全表扫描的成本低于索引扫描,所以mysql最终选择全表扫描
207
208 mysql> select * from employees where name > 'zzz' order by position;
209 mysql> SELECT * FROM information_schema.OPTIMIZER_TRACE;
210
211 查看trace字段可知索引扫描的成本低于全表扫描,所以mysql最终选择索引扫描
212

(↑↑↑引用↑↑↑)

mysql> set session optimizer_trace="enabled=off"; ‐‐关闭trace

索引优化

核心1:索引排序分为Using index和Using filesort,其中Using filesort较慢,如果出现则需要考虑使用Using index进行优化。
Using filesort的出现原因是因为内存大小不足,无法存放需要排序的值,所以采用了外部文件排序,如果能放得下则会采取Using index。

filesort的单路排序和双路排序
单路排序:单路排序是一次性取出所有的数据在内存中排序
双路排序:双路排序是只取出需要排序的字段和索引ID,排序后再根据id回表取出所有的数据
有点类似于聚集索引和辅助索引的查询方式区别
MySQL 通过比较系统变量 max_length_for_sort_data(默认1024字节) 的大小和需要查询的字段总大小来
判断使用哪种排序模式。
如果 字段的总长度小于max_length_for_sort_data ,那么使用 单路排序模式;
如果 字段的总长度大于max_length_for_sort_data ,那么使用 双路排序模式。
简单说就是看放不放得下,放得下就直接都取出来,放不下就只取一部分

核心2:所有的优化原则都是基于B+tree构建的索引树,辅助索引的检索优先于聚集索引,组合索引的最左前缀原则意味着组合原因里每一个索引字段都是前面那个索引字段的细分,必须依序执行,否则无法成功索引。

核心3:范围查询应该放在查询的最后一位,一般来说范围查询后的查询条件无法匹配到对应的索引,如果有多个范围查询可以考虑使用冗余的常量字段来替代范围查询。

核心4:如果非必要,在查询时尽量使用覆盖索引代替select * from 的方式来查找,可以大大提升查询效率。

索引设计原则
1、代码先行,索引后上
2、联合索引尽量覆盖条件
3、不要在小基数字段上建立索引
4、长字符串我们可以采用前缀索引
5、where与order by冲突时优先where
6、基于慢sql查询做优化

Order by与Group by优化
使用级联索引可以使order by和group by的执行速度大幅度加快,但不能违背最左前缀法则,条件和orderby或者groupby必须以索引建立的顺序进行查询。
唯一的例外是

select * from user where name='a' and age=12 order by height,age;

user表中存在一个name,height,age的索引,以上查询是可以走索引的,因为age的值12是常量,在执行的时候会被优化,所以最终走的索引依旧是name,height,age。

分页查询优化

limit分页的执行原理:

select * from user limit 100,10

以上查询mysql会先查询出表中的前100条数据,然后舍弃掉,再查询10条,所以一共是查询了110条数据,在数据量过大时效率会比较慢,可以采用以下优化方式。

select * from user u inner join (select id from user order by uid limit 90000,5) ud
on u.id = ud.id;

这种查询方式只查ID,然后使用ID的主键索引进行回表检索,会大幅度减少查询的数据量,在大数据查询时候有效的避免了filesort方式的排序。

Join关联查询优化
join的查询方式分两种
1.嵌套循环链接(Nested-Loop Join(NLJ))

1. 从驱动表中读取一行数据(如果驱动表表有查询过滤条件的,会从过滤结果里取出一行数据);
2. 从第 1 步的数据中,取出关联字段,到被驱动表中查找;
3. 取出被驱动表中满足条件的行,跟驱动表中获取到的结果合并,作为结果返回给客户端;
4. 重复上面 3 步。

2.基于块的嵌套循环链接(Block Nested-Loop Join(BNL))

1. 把驱动表的所有数据放入到 join_buffer 中
2. 把被驱动表中每一行取出来,跟 join_buffer 中的数据做对比
3. 返回满足 join 条件的数据
ps:如果join_buffer中放不下则分批把驱动表中的数据放入进去

mysql优化时inner join会自动选取较小的表作为驱动表,但并非100%选择正确
left join 左侧为驱动表
right join 右侧为驱动表

优化重点:
关联字段加索引,让mysql做join操作时尽量选择NLJ算法
小表驱动大表,写多表连接sql时如果明确知道哪张表是小表可以用straight_join写法固定连接驱动方式,省去
mysql优化器自己判断的时间

in和exsits优化
优化重点:小表驱动大表

count(*)查询优化
count(*)是最快的查询总数的函数,执行效率如下:

字段有索引:count(*)≈count(1)>count(字段)>count(主键 id) //字段有索引,count(字段)统计走二级索引,二
级索引存储数据比主键索引少,所以count(字段)>count(主键 id)
字段无索引:count(*)≈count(1)>count(主键 id)>count(字段) //字段没有索引count(字段)统计走不了索引,
count(主键 id)还可以走主键索引,所以count(主键 id)>count(字段)

Roy
13 声望1 粉丝

实用主义践行者,所学为所用,所用即所学。