ちょっと硬派なコンピュータフリークのBlogです。

カスタム検索

2015-12-25

オプティマイザトレースによるちょっとディープな快適チューニング生活

メリークリスマス!!今日はMySQL Casual Advent Calendar 2015の25日目をお届けするぞ!!

前回のエントリでは、MySQL 5.7におけるオプティマイザの改良点や新機能についてのスライドを紹介した。MySQL 5.7のオプティマイザの良し悪しは、ぜひみなさんの手で確かめて頂きたい。ところで、オプティマイザといえばひとつ前のバージョンである、MySQL 5.6で追加されたオプティマイザトレースという機能がとても便利だ。使いこなせばクエリチューニングの強い味方になるので、ぜひまだ使ったことがないという方は、一度試してみて欲しい。本ブログではまだ紹介していなかったので、今日はその使い方と見方を紹介しようと思う。

オプティマイザトレースとは

一体何か。ひとことで表せば、オプティマイザがどのような実行計画を検討・比較し、どの実行計画を選択したかということを、詳細に表示してしまう機能だ。

マニュアルは以下のページにある。

MySQL :: MySQL Internals Manual :: 8 Tracing the Optimizer

オプティマイザとレースのマニュアルは、MySQLインターナルマニュアルのコンテンツになっている。インターナルマニュアルなので、エキスパート向けの機能という扱いだ。まあ確かに初心者が見ても中身はサッパリ分からないだろう。本家のリファレンスマニュアルに含まれて居ないということは、もしかするとマイナーバージョンで機能変更等がされる可能性があるのかも知れない。なので、本記事はそういうことがあるかも知れないという前提でもオプティマイザトレースを使ってみようという勇者向けだと言えるかも知れない。

ちなみに、MySQL 5.6と5.7では表示する内容が少し違っていいるが、基本的にそれほどは変わらない。

使い方

オプティマイザトレースの使い方自体は至ってシンプルだ。というかたった3つのステップでトレースを見ることができる。

  1. オプティマイザトレースを有効化する
  2. 解析したいクエリを実行する
  3. トレースを表示する

1
2
3
mysql> SET optimizer_trace="enabled=on";
mysql> SELECT ...以下略 ;
mysql> SELECT * FROM information_schema.optimizer_trace\G

オプティマイザトレースはメモリ上に情報が蓄えられるので、複雑なクエリを解析するときにはメモリの上限に達してしまうかも知れない。そういう場合には、前もってメモリサイズを増やしておくと良いだろう。デフォルトは16KBまでしかメモリを使わないようになっている。

1
mysql> SET optimizer_trace_max_mem_size = 1048576; -- 1MiB

使い方は至って簡単だ。問題は出力されたトレースをどうやって見るかということだ。せっかくのトレースも、見方が分からなければ宝の持ち腐れである。

サンプルとトレースの見方

まずは最もシンプルなクエリ(DUALからの意味のないSELECT)を用いて、トレースの基本的な構造を見てみよう。

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
mysql> select 1;
+---+
| 1 |
+---+
| 1 |
+---+
1 row in set (0.00 sec)
 
mysql> select * from information_schema.optimizer_trace\G
*************************** 1. row ***************************
                            QUERY: select 1
                            TRACE: {
  "steps": [
    {
      "join_preparation": {
        "select#": 1,
        "steps": [
          {
            "expanded_query": "/* select#1 */ select 1 AS `1`"
          }
        ]
      }
    },
    {
      "join_optimization": {
        "select#": 1,
        "steps": [
        ]
      }
    },
    {
      "join_execution": {
        "select#": 1,
        "steps": [
        ]
      }
    }
  ]
}
MISSING_BYTES_BEYOND_MAX_MEM_SIZE: 0
          INSUFFICIENT_PRIVILEGES: 0
1 row in set (0.00 sec)

基本的な構造は、見ての通りJSON形式である。このようにトップオブジェクトにstepsという名前のメンバーがあり、その値としてjoin_preparation、join_optimization、join_executionという名前の3つのメンバーを持つ形となっている。

実は、join_preparationとjoin_executionには目下のところ大した情報は蓄えられて居ない。せいぜいが、クエリをより実態が解りやすい形に書き換える程度である。というわけで、最も価値のある情報は、join_optimizationに入る形になっている。オプティマイザトレースなので、ここがメインディッシュというのはある意味ごく自然だと言える。

次はもう少し複雑な例を見てみよう。MySQL 5.6.27上の出力である。

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
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
mysql> select * from City join Country on City.countrycode = Country.Code and City.id = Country.capital join CountryLanguage on CountryLanguage.countrycode = City.countrycode;
・・・結果略・・・
983 rows in set (0.00 sec)
 
mysql> select * from information_schema.optimizer_trace\G
*************************** 1. row ***************************
                            QUERY: select * from City join Country on City.countrycode = Country.Code and City.id = Country.capital join CountryLanguage on CountryLanguage.countrycode = City.countrycode
                            TRACE: {
  "steps": [
    {
      "join_preparation": {
        "select#": 1,
        "steps": [
          {
            "expanded_query": "/* select#1 */ select `City`.`ID` AS `ID`,`City`.`Name` AS `Name`,`City`.`CountryCode` AS `CountryCode`,`City`.`District` AS `District`,`City`.`Population` AS `Population`,`Country`.`Code` AS `Code`,`Country`.`Name` AS `Name`,`Country`.`Continent` AS `Continent`,`Country`.`Region` AS `Region`,`Country`.`SurfaceArea` AS `SurfaceArea`,`Country`.`IndepYear` AS `IndepYear`,`Country`.`Population` AS `Population`,`Country`.`LifeExpectancy` AS `LifeExpectancy`,`Country`.`GNP` AS `GNP`,`Country`.`GNPOld` AS `GNPOld`,`Country`.`LocalName` AS `LocalName`,`Country`.`GovernmentForm` AS `GovernmentForm`,`Country`.`HeadOfState` AS `HeadOfState`,`Country`.`Capital` AS `Capital`,`Country`.`Code2` AS `Code2`,`CountryLanguage`.`CountryCode` AS `CountryCode`,`CountryLanguage`.`Language` AS `Language`,`CountryLanguage`.`IsOfficial` AS `IsOfficial`,`CountryLanguage`.`Percentage` AS `Percentage` from ((`City` join `Country` on(((`City`.`CountryCode` = `Country`.`Code`) and (`City`.`ID` = `Country`.`Capital`)))) join `CountryLanguage` on((`CountryLanguage`.`CountryCode` = `City`.`CountryCode`)))"
          }
        ]
      }
    },
    {
      "join_optimization": {
        "select#": 1,
        "steps": [
          {
            "transformations_to_nested_joins": {
              "transformations": [
                "JOIN_condition_to_WHERE",
                "parenthesis_removal"
              ],
              "expanded_query": "/* select#1 */ select `City`.`ID` AS `ID`,`City`.`Name` AS `Name`,`City`.`CountryCode` AS `CountryCode`,`City`.`District` AS `District`,`City`.`Population` AS `Population`,`Country`.`Code` AS `Code`,`Country`.`Name` AS `Name`,`Country`.`Continent` AS `Continent`,`Country`.`Region` AS `Region`,`Country`.`SurfaceArea` AS `SurfaceArea`,`Country`.`IndepYear` AS `IndepYear`,`Country`.`Population` AS `Population`,`Country`.`LifeExpectancy` AS `LifeExpectancy`,`Country`.`GNP` AS `GNP`,`Country`.`GNPOld` AS `GNPOld`,`Country`.`LocalName` AS `LocalName`,`Country`.`GovernmentForm` AS `GovernmentForm`,`Country`.`HeadOfState` AS `HeadOfState`,`Country`.`Capital` AS `Capital`,`Country`.`Code2` AS `Code2`,`CountryLanguage`.`CountryCode` AS `CountryCode`,`CountryLanguage`.`Language` AS `Language`,`CountryLanguage`.`IsOfficial` AS `IsOfficial`,`CountryLanguage`.`Percentage` AS `Percentage` from `City` join `Country` join `CountryLanguage` where ((`CountryLanguage`.`CountryCode` = `City`.`CountryCode`) and (`City`.`CountryCode` = `Country`.`Code`) and (`City`.`ID` = `Country`.`Capital`))"
            }
          },
          {
            "condition_processing": {
              "condition": "WHERE",
              "original_condition": "((`CountryLanguage`.`CountryCode` = `City`.`CountryCode`) and (`City`.`CountryCode` = `Country`.`Code`) and (`City`.`ID` = `Country`.`Capital`))",
              "steps": [
                {
                  "transformation": "equality_propagation",
                  "resulting_condition": "(multiple equal(`CountryLanguage`.`CountryCode`, `City`.`CountryCode`, `Country`.`Code`) and multiple equal(`City`.`ID`, `Country`.`Capital`))"
                },
                {
                  "transformation": "constant_propagation",
                  "resulting_condition": "(multiple equal(`CountryLanguage`.`CountryCode`, `City`.`CountryCode`, `Country`.`Code`) and multiple equal(`City`.`ID`, `Country`.`Capital`))"
                },
                {
                  "transformation": "trivial_condition_removal",
                  "resulting_condition": "(multiple equal(`CountryLanguage`.`CountryCode`, `City`.`CountryCode`, `Country`.`Code`) and multiple equal(`City`.`ID`, `Country`.`Capital`))"
                }
              ]
            }
          },
          {
            "table_dependencies": [
              {
                "table": "`City`",
                "row_may_be_null": false,
                "map_bit": 0,
                "depends_on_map_bits": [
                ]
              },
              {
                "table": "`Country`",
                "row_may_be_null": false,
                "map_bit": 1,
                "depends_on_map_bits": [
                ]
              },
              {
                "table": "`CountryLanguage`",
                "row_may_be_null": false,
                "map_bit": 2,
                "depends_on_map_bits": [
                ]
              }
            ]
          },
          {
            "ref_optimizer_key_uses": [
              {
                "table": "`City`",
                "field": "ID",
                "equals": "`Country`.`Capital`",
                "null_rejecting": true
              },
              {
                "table": "`City`",
                "field": "CountryCode",
                "equals": "`Country`.`Code`",
                "null_rejecting": false
              },
              {
                "table": "`City`",
                "field": "CountryCode",
                "equals": "`CountryLanguage`.`CountryCode`",
                "null_rejecting": false
              },
              {
                "table": "`City`",
                "field": "ID",
                "equals": "`Country`.`Capital`",
                "null_rejecting": true
              },
              {
                "table": "`Country`",
                "field": "Code",
                "equals": "`CountryLanguage`.`CountryCode`",
                "null_rejecting": false
              },
              {
                "table": "`Country`",
                "field": "Code",
                "equals": "`City`.`CountryCode`",
                "null_rejecting": false
              },
              {
                "table": "`CountryLanguage`",
                "field": "CountryCode",
                "equals": "`Country`.`Code`",
                "null_rejecting": false
              },
              {
                "table": "`CountryLanguage`",
                "field": "CountryCode",
                "equals": "`City`.`CountryCode`",
                "null_rejecting": false
              },
              {
                "table": "`CountryLanguage`",
                "field": "CountryCode",
                "equals": "`Country`.`Code`",
                "null_rejecting": false
              },
              {
                "table": "`CountryLanguage`",
                "field": "CountryCode",
                "equals": "`City`.`CountryCode`",
                "null_rejecting": false
              }
            ]
          },
          {
            "rows_estimation": [
              {
                "table": "`City`",
                "table_scan": {
                  "rows": 4188,
                  "cost": 25
                }
              },
              {
                "table": "`Country`",
                "table_scan": {
                  "rows": 239,
                  "cost": 6
                }
              },
              {
                "table": "`CountryLanguage`",
                "table_scan": {
                  "rows": 984,
                  "cost": 6
                }
              }
            ]
          },
          {
            "considered_execution_plans": [
              {
                "plan_prefix": [
                ],
                "table": "`Country`",
                "best_access_path": {
                  "considered_access_paths": [
                    {
                      "access_type": "ref",
                      "index": "PRIMARY",
                      "usable": false,
                      "chosen": false
                    },
                    {
                      "access_type": "scan",
                      "rows": 239,
                      "cost": 53.8,
                      "chosen": true
                    }
                  ]
                },
                "cost_for_plan": 53.8,
                "rows_for_plan": 239,
                "rest_of_plan": [
                  {
                    "plan_prefix": [
                      "`Country`"
                    ],
                    "table": "`CountryLanguage`",
                    "best_access_path": {
                      "considered_access_paths": [
                        {
                          "access_type": "ref",
                          "index": "PRIMARY",
                          "rows": 1,
                          "cost": 239.2,
                          "chosen": true
                        },
                        {
                          "access_type": "ref",
                          "index": "CountryCode",
                          "rows": 1,
                          "cost": 239.2,
                          "chosen": false
                        },
                        {
                          "access_type": "scan",
                          "using_join_cache": true,
                          "rows": 738,
                          "cost": 35333,
                          "chosen": false
                        }
                      ]
                    },
                    "cost_for_plan": 340.6,
                    "rows_for_plan": 239,
                    "rest_of_plan": [
                      {
                        "plan_prefix": [
                          "`Country`",
                          "`CountryLanguage`"
                        ],
                        "table": "`City`",
                        "best_access_path": {
                          "considered_access_paths": [
                            {
                              "access_type": "ref",
                              "index": "PRIMARY",
                              "rows": 1,
                              "cost": 239.2,
                              "chosen": true
                            },
                            {
                              "access_type": "ref",
                              "index": "CountryCode",
                              "rows": 1,
                              "cost": 239.2,
                              "chosen": false
                            },
                            {
                              "access_type": "scan",
                              "using_join_cache": true,
                              "rows": 3141,
                              "cost": 150381,
                              "chosen": false
                            }
                          ]
                        },
                        "added_to_eq_ref_extension": true,
                        "cost_for_plan": 627.4,
                        "rows_for_plan": 239,
                        "chosen": true
                      }
                    ]
                  }
                ]
              },
              {
                "plan_prefix": [
                ],
                "table": "`CountryLanguage`",
                "best_access_path": {
                  "considered_access_paths": [
                    {
                      "access_type": "ref",
                      "index": "PRIMARY",
                      "usable": false,
                      "chosen": false
                    },
                    {
                      "access_type": "ref",
                      "index": "CountryCode",
                      "usable": false,
                      "chosen": false
                    },
                    {
                      "access_type": "scan",
                      "rows": 984,
                      "cost": 202.8,
                      "chosen": true
                    }
                  ]
                },
                "cost_for_plan": 202.8,
                "rows_for_plan": 984,
                "rest_of_plan": [
                  {
                    "plan_prefix": [
                      "`CountryLanguage`"
                    ],
                    "table": "`Country`",
                    "best_access_path": {
                      "considered_access_paths": [
                        {
                          "access_type": "ref",
                          "index": "PRIMARY",
                          "rows": 1,
                          "cost": 984.2,
                          "chosen": true
                        },
                        {
                          "access_type": "scan",
                          "using_join_cache": true,
                          "rows": 180,
                          "cost": 35443,
                          "chosen": false
                        }
                      ]
                    },
                    "cost_for_plan": 1383.6,
                    "rows_for_plan": 984,
                    "pruned_by_cost": true
                  },
                  {
                    "plan_prefix": [
                      "`CountryLanguage`"
                    ],
                    "table": "`City`",
                    "best_access_path": {
                      "considered_access_paths": [
                        {
                          "access_type": "ref",
                          "index": "PRIMARY",
                          "usable": false,
                          "chosen": false
                        },
                        {
                          "access_type": "ref",
                          "index": "CountryCode",
                          "rows": 1,
                          "cost": 984.2,
                          "chosen": true
                        },
                        {
                          "access_type": "scan",
                          "using_join_cache": true,
                          "rows": 3141,
                          "cost": 618387,
                          "chosen": false
                        }
                      ]
                    },
                    "cost_for_plan": 1383.6,
                    "rows_for_plan": 984,
                    "pruned_by_cost": true
                  }
                ]
              },
              {
                "plan_prefix": [
                ],
                "table": "`City`",
                "best_access_path": {
                  "considered_access_paths": [
                    {
                      "access_type": "ref",
                      "index": "PRIMARY",
                      "usable": false,
                      "chosen": false
                    },
                    {
                      "access_type": "ref",
                      "index": "CountryCode",
                      "usable": false,
                      "chosen": false
                    },
                    {
                      "access_type": "scan",
                      "rows": 4188,
                      "cost": 862.6,
                      "chosen": true
                    }
                  ]
                },
                "cost_for_plan": 862.6,
                "rows_for_plan": 4188,
                "pruned_by_cost": true
              }
            ]
          },
          {
            "attaching_conditions_to_tables": {
              "original_condition": "((`City`.`ID` = `Country`.`Capital`) and (`CountryLanguage`.`CountryCode` = `Country`.`Code`) and (`City`.`CountryCode` = `Country`.`Code`))",
              "attached_conditions_computation": [
              ],
              "attached_conditions_summary": [
                {
                  "table": "`Country`",
                  "attached": "(`Country`.`Capital` is not null)"
                },
                {
                  "table": "`CountryLanguage`",
                  "attached": null
                },
                {
                  "table": "`City`",
                  "attached": "(`City`.`CountryCode` = `Country`.`Code`)"
                }
              ]
            }
          },
          {
            "refine_plan": [
              {
                "table": "`Country`",
                "access_type": "table_scan"
              },
              {
                "table": "`CountryLanguage`"
              },
              {
                "table": "`City`"
              }
            ]
          }
        ]
      }
    },
    {
      "join_execution": {
        "select#": 1,
        "steps": [
        ]
      }
    }
  ]
}
MISSING_BYTES_BEYOND_MAX_MEM_SIZE: 0
          INSUFFICIENT_PRIVILEGES: 0
1 row in set (0.00 sec)

join_optimizationの中身を見ていこう。基本的には値はstepsという名前のメンバーを持つオブジェクトになっており、stepsの値は配列である。

"select#": 1の部分は、EXPLAINのidフィールドと同じである。MySQLでは、SELECTの単位はJOINである。JOINはいくつのテーブルを結合しても、idは変わらない。サブクエリやUNIONではidが変化する。idの詳しい意味については過去に書いた「MySQLのEXPLAINを徹底解説!!」というエントリを参照して欲しい。

次のtransformations_to_nested_joinsでは、JOINのON句の条件がWHERE句へと移動されている。これは文法上等価な変換であり、実行計画を探索する前に施される処理ということになる。ちなみに、MySQL 5.7ではtransformations_to_nested_joinsは、join_optimizationではなく、join_preparationへと移動されている。

condition_processingではオプティマイザが実行する各種の等価変換が記述される。上記の例では、equality_propagation(等価比較に対する推移律)を適用した結果、`CountryLanguage`.`CountryCode`, `City`.`CountryCode`, `Country`.`Code`という3つのカラムはすべて同じ値であることが判明したため、同じグループにまとめられている。constant_propagationは、推移律の対象となる値に、定数が含まれている場合に適用される最適化である。trivial_condition_removalでは、WHERE 1=1のような不要な条件が省かれる。

table_dependenciesでは、テーブルの依存関係が示される。これはOUTER JOINで意味がある項目であり、今回はINNER JOINなので何も意味のある情報は含まれていない。OUTER JOINの場合には、depends_on_map_bitsに依存するテーブルのmap_bitが表示される。

ref_optimizer_key_usesは、利用可能なインデックスの情報、rows_estimationはテーブルごとの行数の見積もりだ。

そして、オプティマイザトレースで最も重要な項目が、considered_execution_plansだ。値は配列になっていて、それぞれがJOINの順序を変えた場合のコストなどが表示される。配列の要素のオブジェクトは入れ子になっていて、次のような構造になっている。

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
{
  "plan_prefix": [
  ],
  "table": "`Country`",
  "best_access_path": {
    "considered_access_paths": [
      {
        "access_type": "ref",
        "index": "PRIMARY",
        "usable": false,
        "chosen": false
      },
      {
        "access_type": "scan",
        "rows": 239,
        "cost": 53.8,
        "chosen": true
      }
    ]
  },
  "cost_for_plan": 53.8,
  "rows_for_plan": 239,
  "rest_of_plan": [
    下位の実行計画
  ]

plan_prefixは、既にアクセスされたテーブルである。駆動表といえば解りやすいだろうか。この実行計画では、Countryテーブルが最初にアクセスされるため、plan_prefixは空となっている。tableには現在アクセスしているテーブルが、best_access_pathにはこのテーブルへアクセスするのに使われる実行計画に関する情報がまとめられている。値は配列になっていて、要素は次のようなメンバーを持つオブジェクトである。

access_typeは、レコードアクセスタイプとも呼ばれ、対象のテーブルに対してどのような方法でアクセスするかを示す。この例では、ref(ユニークでないインデックスを用いた等価比較)とscan(テーブルスキャン)が表示されている。アクセスタイプはEXPLAINと同じなので、た「MySQLのEXPLAINを徹底解説!!」を参照して欲しい。それぞれのアクセスタイプが使用可能か、使用可能な場合コストはいくつか、オプティマイザによって選択されたかといった情報が含まれている。この実行計画では、Countryテーブルをスキャンするものが選択されている。その結果、このテーブルへアクセスするコストが53.8であるということが分かった。

最後の要素、rest_of_planは、このテーブルの後にアクセスされるテーブルの情報である。rest_of_planの各要素も、同じ構造を持ったオブジェクトになっている。rest_of_planのplan_prefixには、これまでアクセスしたテーブルが登場することになる。最終的なクエリのコストは、rest_of_planのネストが一番深いところのものを見て判定する。例えば一つ目の実行計画では、ネストの一番深いところのコストは627.4となっており、これが実行計画全体のコストとなる。

considered_execution_plansの、2つ目以降の実行計画は、rest_of_planでpruned_by_costがtrueになっている。これは、コストの計算が途中であるにも関わらず、既に判明している他の実行計画よりもコストがかかることが判明してしまったため、途中でコストの計算を切り上げたことを意味する。例えば、2つ目の実行計画(CountryLanguageからJOINするもの)は、2つ目のテーブルをJOINしたところで、良い方でもコストは1383.6となっており、一つ目の627.4よりもコストが大きい。これ以上JOINをするとさらにコストは増えるため、この実行計画には将来性はないことが分かる。要するに、pruned_by_costがtrueな実行計画は、選択されなかったということになる。ちなみに、rest_of_planも配列になっていて、今回のように3つ以上のテーブルをJOINする場合、2つ目にアクセスするテーブルは2通りあるため、rest_of_planには2通りの実行計画が含まれることになる。

attaching_conditions_to_tablesは、それぞれのテーブルにどのような比較の条件が適用されるかを示している。アクセスタイプによるものだけでなく、インデックスが適用できない条件などがあれば、ここに表示される。

refine_planは、最終的にJOINがどのような順序で実行されるかを簡素に示したものだ。

まとめ

オプティマイザトレースは、まだ若干複雑ではあるが、EXPLAINだけでは分からないコストの情報が含まれており、使いこなせば強力な解析ツールになり得るものだ。JOINの順序が思うように行かない場合、わざわざSTRAIGHT_JOINなどのヒントを指定して、JOINの順序を入れ替えてからEXPLAINで解析するといったような手間は、もはや必要ない。オプティマイザトレースを使えば、オプティマイザが検討した実行計画の一覧が見られるからだ。エキスパートの読者諸氏は、ぜひ現場でもオプティマイザトレースを活用してみて欲しい。

今回はJOINだけのクエリをサンプルとしてお見せしたが、当然ながらサブクエリ等があれば様相は変わってくる。サブクエリを含んだものに関しては、機会を改めて紹介したいと思う。

0 コメント:

コメントを投稿