知一杂谈

MySQL语句优化分析

字数统计: 1.6k阅读时长: 8 min
2020/07/13

环境

1
2
3
4
5
6
7
8
9
10
11
CREATE TABLE `people` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(16) DEFAULT NULL,
`pass` varchar(256) DEFAULT NULL,
`card` varchar(32) DEFAULT NULL,
`age` int(11) NOT NULL,
`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `card_UNIQUE` (`card`),
KEY `name_age_key` (`name`,`age`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

插入十一万条随机数据

1
INSERT INTO `people` (`name`,`pass`,`card`,`age`) VALUES ('强欣笑','eSbJgrXAVjEy','143185791961386356',53), ... ;

EXPLAIN 查询计划

https://www.cnblogs.com/DataArt/p/10215663.html

OPTIMIZER_TRACE 使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
-- 1. 打开 optimizer_trace功能,默认是关着的
SHOW VARIABLES LIKE 'optimizer_trace';
SET optimizer_trace="enabled=on";

-- 2. 执行查询
SELECT name,
count(*) cnt,
group_concat(distinct card) gd_card
FROM people
WHERE age > 10
AND age > -1
AND 1=1
AND age < 99
GROUP BY name
HAVING cnt > 1
ORDER BY cnt DESC limit 20000;

-- 3. 从OPTIMIZER_TRACE表中查看上一个查询的优化过程
SELECT * FROM information_schema.OPTIMIZER_TRACE;

-- 4. 还要用的话就重复2、3
-- ...

-- 5. 不用了就关掉
SET optimizer_trace="enabled=off";

解读

MySQL分为以上几个模块,主要是 server 层和存储引擎层,service 层又分为连接器、分析器、优化器、执行器和查询缓存几个部分。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
{
"steps": [
{
"join_preparation": {
"select#": 1,
"steps": [
{
"expanded_query": "/* select#1 */ select `people`.`name` AS `name`,count(0) AS `cnt`,group_concat(distinct `people`.`card` separator ',') AS `gd_card` from `people` where ((`people`.`age` > 10) and (`people`.`age` > -(1)) and (1 = 1) and (`people`.`age` < 99)) group by `people`.`name` having (`cnt` > 1) order by `cnt` desc limit 20000"
}
]
}
},
{
"join_optimization": {
"select#": 1,
"steps": [
{
// 处理搜索条件
"condition_processing": {
"condition": "WHERE",
// 原始搜索条件
"original_condition": "((`people`.`age` > 10) and (`people`.`age` > -(1)) and (1 = 1) and (`people`.`age` < 99))",
"steps": [
{
// 等值传递转换
"transformation": "equality_propagation",
"resulting_condition": "((`people`.`age` > 10) and (`people`.`age` > -(1)) and (1 = 1) and (`people`.`age` < 99))"
},
{
// 常量传递转换
"transformation": "constant_propagation",
"resulting_condition": "((`people`.`age` > 10) and (`people`.`age` > -(1)) and (1 = 1) and (`people`.`age` < 99))"
},
{
// 去除没用的条件
"transformation": "trivial_condition_removal",
"resulting_condition": "((`people`.`age` > 10) and (`people`.`age` > -(1)) and (`people`.`age` < 99))"
}
]
}
},
{
"condition_processing": {
"condition": "HAVING",
"original_condition": "(`cnt` > 1)",
"steps": [
{
"transformation": "constant_propagation",
"resulting_condition": "(`cnt` > 1)"
},
{
"transformation": "trivial_condition_removal",
"resulting_condition": "(`cnt` > 1)"
}
]
}
},
{
// 替换虚拟生成列
"substitute_generated_columns": {}
},
{
// 表的依赖信息
"table_dependencies": [
{
"table": "`people`",
"row_may_be_null": false,
"map_bit": 0,
"depends_on_map_bits": []
}
]
},
{
"ref_optimizer_key_uses": []
},
{
// 预估不同单表访问方法的访问成本
"rows_estimation": [
{
"table": "`people`",
"const_keys_added": {
"keys": [
"name_age_key"
],
"cause": "group_by"
},
"range_analysis": {
// 全表扫描的行数及成本
"table_scan": {
"rows": 109674,
"cost": 22482
},
// 分析可能使用的索引
"potential_range_indexes": [
{
"index": "PRIMARY",
"usable": false,
"cause": "not_applicable"
},
{
"index": "card_UNIQUE",
"usable": false,
"cause": "not_applicable"
},
{
"index": "name_age_key",
"usable": true,
"key_parts": [
"name",
"age",
"id"
]
}
],
"setup_range_conditions": [],
"group_index_range": {
"chosen": false,
"cause": "not_applicable_aggregate_function"
},
// 分析各种可能使用的索引的成本
"analyzing_range_alternatives": {
"range_scan_alternatives": [
{
"index": "name_age_key",
"chosen": false,
"cause": "unknown"
}
],
"analyzing_roworder_intersect": {
"usable": false,
"cause": "too_few_roworder_scans"
}
}
}
}
]
},
{
"considered_execution_plans": [
{
"plan_prefix": [],
"table": "`people`",
"best_access_path": {
"considered_access_paths": [
{
"rows_to_scan": 109674,
"access_type": "scan",
"resulting_rows": 4060.8,
"cost": 22480,
"chosen": true
}
]
},
"condition_filtering_pct": 100,
"rows_for_plan": 4060.8,
"cost_for_plan": 22480,
"chosen": true
}
]
},
{
// 尝试给查询添加一些其他的查询条件
"attaching_conditions_to_tables": {
"original_condition": "((`people`.`age` > 10) and (`people`.`age` > -(1)) and (`people`.`age` < 99))",
"attached_conditions_computation": [],
"attached_conditions_summary": [
{
"table": "`people`",
"attached": "((`people`.`age` > 10) and (`people`.`age` > -(1)) and (`people`.`age` < 99))"
}
]
}
},
{
"clause_processing": {
"clause": "ORDER BY",
"original_clause": "`cnt` desc",
"items": [
{
"item": "count(0)"
}
],
"resulting_clause_is_simple": false,
"resulting_clause": "`cnt` desc"
}
},
{
"clause_processing": {
"clause": "GROUP BY",
"original_clause": "`people`.`name`",
"items": [
{
"item": "`people`.`name`"
}
],
"resulting_clause_is_simple": true,
"resulting_clause": "`people`.`name`"
}
},
{
"reconsidering_access_paths_for_index_ordering": {
"clause": "GROUP BY",
"steps": [],
"index_order_summary": {
"table": "`people`",
"index_provides_order": true,
"order_direction": "asc",
"index": "name_age_key",
"plan_changed": true,
"access_type": "index"
}
}
},
{
"refine_plan": [
{
"table": "`people`"
}
]
},
{
"creating_tmp_table": {
"tmp_table_info": {
"table": "intermediate_tmp_table",
"row_length": 130,
"key_length": 0,
"unique_constraint": false,
"location": "memory (heap)",
"row_limit_estimate": 129055
}
}
},
{
"sort_using_internal_table": {
"condition_for_sort": "(`cnt` > 1)",
"having_after_sort": null
}
}
]
}
},
{
"join_execution": {
"select#": 1,
"steps": [
{
"creating_tmp_table": {
"tmp_table_info": {
"table": "intermediate_tmp_table",
"row_length": 84,
"key_length": 0,
"unique_constraint": false,
"location": "disk (InnoDB)",
"record_format": "packed"
}
}
},
{
"filesort_information": [
{
"direction": "desc",
"table": "intermediate_tmp_table",
"field": "cnt"
}
],
"filesort_priority_queue_optimization": {
"usable": false,
"cause": "not applicable (no LIMIT)"
},
"filesort_execution": [],
"filesort_summary": {
// 排序过程中持有的行数
"rows": 27045,
// 参与排序的行数,InnoDB 返回的行数
"examined_rows": 34054,
// 排序使用的临时文件数量
"number_of_tmp_files": 24,
// 内存排序使用的内存大小
"sort_buffer_size": 25744,
// 排序模式
"sort_mode": "<sort_key, rowid>"
}
}
]
}
}
]
}

优化过程大致分为了三个阶段:

prepare阶段

optimize阶段

execute阶段

我们所说的基于成本的优化主要集中在optimize阶段,对于单表查询来说,我们主要关注optimize阶段的”rows_estimation”这个过程,这个过程深入分析了对单表查询的各种执行方案的成本;对于多表连接查询来说,我们更多需要关注”considered_execution_plans”这个过程,这个过程里会写明各种不同的连接方式所对应的成本。反正优化器最终会选择成本最低的那种方案来作为最终的执行计划,也就是我们使用EXPLAIN语句所展现出的那种方案。

参考

https://www.cnblogs.com/taosiyu/p/13206378.html

原文作者:noogel

原文链接:https://noogel.xyz/2020/07/13/1.html

发表日期:2020-07-13

版权声明:本文采用知识共享署名-非商业性使用 4.0 国际许可协议进行许可

CATALOG
  1. 1. 环境
  2. 2. EXPLAIN 查询计划
  3. 3. OPTIMIZER_TRACE 使用
    1. 3.1. 解读
  4. 4. 参考