Este artículo lo lleva a comprender el efecto del artefacto de seguimiento del optimizador de MySQL

Prefacio:

Para MySQL 5.6 y versiones anteriores, el optimizador de consultas es como una caja negra. Solo puede EXPLAINver el plan de ejecución que el optimizador decide usar a través de la declaración, pero no puede saber por qué tomó esta decisión. Esto es un desastre para algunos amigos a quienes les gusta llegar al fondo del asunto: "Siento que usar otros esquemas de ejecución es EXPLAINmejor que el esquema de salida. ¿Por qué el optimizador toma una decisión diferente a la que pienso?". Este artículo presenta principalmente optimizer traceel todo el proceso de usar el optimizador de vista para generar un plan de ejecución

La magia de la tabla de seguimiento del optimizador

En MySQL 5.6 y versiones posteriores, el tío que diseñó MySQL propuso cuidadosamente una optimizer tracefunción para estos pequeños socios. Esta función nos permite ver fácilmente todo el proceso del optimizador generando el plan de ejecución. La apertura de esta función está relacionada con El apagado es determinado por las variables del sistema optimizer_trace, echemos un vistazo:

mysql> show variables like 'optimizer_trace';
+-----------------+--------------------------+
| Variable_name   | Value                    |
+-----------------+--------------------------+
| optimizer_trace | enabled=off,one_line=off |
+-----------------+--------------------------+
1 row in set (0.01 sec)

Puede ver enabledel valor off, que indica que esta función está deshabilitada por defecto.

小提示:
one_lineEl valor de es para controlar el formato de salida. Si se establece en , ontoda la salida se mostrará en una línea, lo que no es adecuado para la lectura humana, así que mantengamos su valor predeterminado desactivado.

Si desea habilitar esta función, primero debe cambiar el valor de habilitado a activado, así:

mysql> SET optimizer_trace="enabled=on";
Query OK, 0 rows affected (0.00 sec)

Luego podemos ingresar la declaración de consulta que queremos ver el proceso de optimización.Después de ejecutar la declaración de consulta, podemos ver el proceso de optimización completo en la tabla information_schemadebajo de la base de datos . OPTIMIZER_TRACEEsta OPTIMIZER_TRACEtabla tiene 4 columnas, a saber:

  • QUERY: Indica nuestra sentencia de consulta.
  • TRACE: texto con formato JSON que representa el proceso de optimización.
  • MISSING_BYTES_BEYOND_MAX_MEM_SIZE: Dado que el proceso de optimización puede generar muchos resultados, si se excede un cierto límite, el texto redundante no se mostrará.Este campo muestra el número de bytes de texto ignorados.
  • INSUFFICIENT_PRIVILEGES: Indica si no hay permiso para ver el proceso de optimización. El valor predeterminado es 0, y solo en algunos casos especiales será 1. No nos importa el valor de este campo por el momento.

Los pasos completos para usar la función de seguimiento del optimizador se resumen a continuación:

Paso 1: Active la función de seguimiento del optimizador (está desactivada de forma predeterminada):

mysql> SET optimizer_trace="enabled=on";
Query OK, 0 rows affected (0.01 sec)

Paso 2: Ingrese su propia consulta aquí

SELECT	...;

Paso 3: Ver el proceso de optimización de la consulta anterior de la tabla OPTIMIZER_TRACE

SELECT * FROM information_schema.OPTIMIZER_TRACE;

Paso 4: Es posible que también deba observar el proceso de optimización de la ejecución de otras declaraciones, repita los pasos 2 y 3 anteriores

Paso 5: cuando deje de ver el proceso de optimización de la declaración, desactive la función de seguimiento del optimizador

mysql> SET optimizer_trace="enabled=off";
Query OK, 0 rows affected (0.01 sec)

Ahora tenemos una declaración de consulta con muchas condiciones de búsqueda, y su plan de ejecución es el siguiente:

mysql> EXPLAIN SELECT * FROM s1 WHERE key1 > 'z' AND  key2 < 1000000 AND key3 IN ('aa', 'bb', 'cb') AND   common_field = 'abc';
+----+-------------+-------+------------+-------+----------------------------+----------+---------+------+------+----------+------------------------------------+
| id | select_type | table | partitions | type  | possible_keys              | key      | key_len | ref  | rows | filtered | Extra                              |
+----+-------------+-------+------------+-------+----------------------------+----------+---------+------+------+----------+------------------------------------+
|  1 | SIMPLE      | s1    | NULL       | range | idx_key2,idx_key1,idx_key3 | idx_key1 | 403     | NULL |    1 |     5.00 | Using index condition; Using where |
+----+-------------+-------+------------+-------+----------------------------+----------+---------+------+------+----------+------------------------------------+
1 row in set, 1 warning (0.00 sec)

Se puede ver que hay 3 índices que pueden ser utilizados por esta consulta, entonces, ¿por qué el optimizador finalmente eligió en lugar de idx_key1otros índices o el escaneo directo de la tabla completa? En este momento, puede usar otpimzer tracela función para ver el proceso de trabajo específico del optimizador:

mysql> SET optimizer_trace="enabled=on";
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT * FROM s1 WHERE key1 > 'z' AND  key2 < 1000000 AND key3 IN ('aa', 'bb', 'cb') AND   common_field = 'abc';
Empty set (0.00 sec)

mysql> SELECT * FROM information_schema.OPTIMIZER_TRACE\G   

Veamos directamente OPTIMIZER_TRACEel resultado obtenido a través de la tabla de consulta (le #expliqué algunos de los puntos más importantes en el proceso de optimización en forma de comentarios seguidos de su uso, y debe prestarle atención):

*************************** 1. row ***************************
# 分析的查询语句是什么
QUERY: SELECT * FROM s1 WHERE key1 > 'z' AND  key2 < 1000000 AND key3 IN ('aa', 'bb', 'cb') AND   common_field = 'abc'
# 优化的具体过程
TRACE: {
  "steps": [
    {
      "join_preparation": {	# prepare阶段
        "select#": 1,
        "steps": [
          {
            "IN_uses_bisection": true
          },
          {
            "expanded_query": "/* select#1 */ select `s1`.`id` AS `id`,`s1`.`key1` AS `key1`,`s1`.`key2` AS `key2`,`s1`.`key3` AS `key3`,`s1`.`key_part1` AS `key_part1`,`s1`.`key_part2` AS `key_part2`,`s1`.`key_part3` AS `key_part3`,`s1`.`common_field` AS `common_field` from `s1` where ((`s1`.`key1` > 'z') and (`s1`.`key2` < 1000000) and (`s1`.`key3` in ('aa','bb','cb')) and (`s1`.`common_field` = 'abc'))"
          }
        ]
      }
    },
    {
      "join_optimization": {  # optimize阶段
        "select#": 1,
        "steps": [
          {
            "condition_processing": { # 处理搜索条件
              "condition": "WHERE",
              # 原始搜索条件
              "original_condition": "((`s1`.`key1` > 'z') and (`s1`.`key2` < 1000000) and (`s1`.`key3` in ('aa','bb','cb')) and (`s1`.`common_field` = 'abc'))",
              "steps": [
                {
                # 等值传递转换
                  "transformation": "equality_propagation",
                  "resulting_condition": "((`s1`.`key1` > 'z') and (`s1`.`key2` < 1000000) and (`s1`.`key3` in ('aa','bb','cb')) and multiple equal('abc', `s1`.`common_field`))"
                },
                {
                # 常量传递转换
                  "transformation": "constant_propagation",
                  "resulting_condition": "((`s1`.`key1` > 'z') and (`s1`.`key2` < 1000000) and (`s1`.`key3` in ('aa','bb','cb')) and multiple equal('abc', `s1`.`common_field`))"
                },
                {
                # 去除没用的条件
                  "transformation": "trivial_condition_removal",
                  "resulting_condition": "((`s1`.`key1` > 'z') and (`s1`.`key2` < 1000000) and (`s1`.`key3` in ('aa','bb','cb')) and multiple equal('abc', `s1`.`common_field`))"
                }
              ]
            }
          },
          {
          # 替换虚拟生成列
            "substitute_generated_columns": {
            }
          },
          {
          # 表的依赖信息
            "table_dependencies": [
              {
                "table": "`s1`",
                "row_may_be_null": false,
                "map_bit": 0,
                "depends_on_map_bits": [
                ]
              }
            ]
          },
          {
            "ref_optimizer_key_uses": [
            ]
          },
          {
          # 预估不同单表访问方法的访问成本
            "rows_estimation": [
              {
                "table": "`s1`",
                "range_analysis": {
                  "table_scan": {
                    "rows": 20250,
                    "cost": 2051.35
                  },
                   # 分析可能使用的索引
                  "potential_range_indexes": [
                    {
                      "index": "PRIMARY", # 主键不可用
                      "usable": false,
                      "cause": "not_applicable"
                    },
                    {
                      "index": "idx_key2",# idx_key2可能被使用
                      "usable": true,
                      "key_parts": [
                        "key2"
                      ]
                    },
                    {
                      "index": "idx_key1", # idx_key1可能被使用
                      "usable": true,
                      "key_parts": [
                        "key1",
                        "id"
                      ]
                    },
                    {
                      "index": "idx_key3", # idx_key3可能被使用
                      "usable": true,
                      "key_parts": [
                        "key3",
                        "id"
                      ]
                    },
                    {
                      "index": "idx_key_part", # idx_key_part不可用
                      "usable": false,
                      "cause": "not_applicable"
                    }
                  ],
                  "setup_range_conditions": [
                  ],
                  "group_index_range": {
                    "chosen": false,
                    "cause": "not_group_by_or_distinct"
                  },
                  "skip_scan_range": {
                    "potential_skip_scan_indexes": [
                      {
                        "index": "idx_key2",
                        "usable": false,
                        "cause": "query_references_nonkey_column"
                      },
                      {
                        "index": "idx_key1",
                        "usable": false,
                        "cause": "query_references_nonkey_column"
                      },
                      {
                        "index": "idx_key3",
                        "usable": false,
                        "cause": "query_references_nonkey_column"
                      }
                    ]
                  },
                  # 分析各种可能使用的索引的成本
                  "analyzing_range_alternatives": {
                    "range_scan_alternatives": [
                      {
                      # 使用idx_key2的成本分析
                        "index": "idx_key2",
                        # 使用idx_key2的范围区间
                        "ranges": [
                          "NULL < key2 < 1000000"
                        ],
                        "index_dives_for_eq_ranges": true,# 是否使用index dive
                        "rowid_ordered": false,# 使用该索引获取的记录是否按照主键排序
                        "using_mrr": false, # 是否使用mrr
                        "index_only": false, # 是否是索引覆盖访问
                        "in_memory": 1,
                        "rows": 10125,# 使用该索引获取的记录条数
                        "cost": 3544.01,# 使用该索引的成本
                        "chosen": false,  # 使用该索引的成本
                        "cause": "cost" # 因为成本太大所以不选择该索引
                      },
                      {
                      # 使用idx_key1的成本分析
                        "index": "idx_key1",
                         # 使用idx_key1的范围区间
                        "ranges": [
                          "'z' < key1"
                        ],
                        "index_dives_for_eq_ranges": true,# 同上
                        "rowid_ordered": false,# 同上
                        "using_mrr": false,# 同上
                        "index_only": false,# 同上
                        "in_memory": 1,
                        "rows": 1,# 同上
                        "cost": 0.61,# 同上
                        "chosen": true# 是否选择该索引
                      },
                      {
                       # 使用idx_key3的成本分析
                        "index": "idx_key3",
                          # 使用idx_key3的范围区间
                        "ranges": [
                          "key3 = 'aa'",
                          "key3 = 'bb'",
                          "key3 = 'cb'"
                        ],
                        "index_dives_for_eq_ranges": true,# 同上
                        "rowid_ordered": false,# 同上
                        "using_mrr": false,# 同上
                        "index_only": false,# 同上
                        "in_memory": 1,
                        "rows": 3,# 同上
                        "cost": 1.81,# 同上
                        "chosen": false,# 同上
                        "cause": "cost"# 同上
                      }
                    ],
                    # 分析使用索引合并的成本
                    "analyzing_roworder_intersect": {
                      "usable": false,
                      "cause": "too_few_roworder_scans"
                    }
                  },
                  # 对于上述单表查询s1最优的访问方法
                  "chosen_range_access_summary": {
                    "range_access_plan": {
                      "type": "range_scan",
                      "index": "idx_key1",
                      "rows": 1,
                      "ranges": [
                        "'z' < key1"
                      ]
                    },
                    "rows_for_plan": 1,
                    "cost_for_plan": 0.61,
                    "chosen": true
                  }
                }
              }
            ]
          },
          {
          
            # 分析各种可能的执行计划
            #(对多表查询这可能有很多种不同的方案,单表查询的方案上边已经分析过了,直接选取idx_key1就好)
            "considered_execution_plans": [
              {
                "plan_prefix": [
                ],
                "table": "`s1`",
                "best_access_path": {
                  "considered_access_paths": [
                    {
                      "rows_to_scan": 1,
                      "access_type": "range",
                      "range_details": {
                        "used_index": "idx_key1"
                      },
                      "resulting_rows": 1,
                      "cost": 0.71,
                      "chosen": true
                    }
                  ]
                },
                "condition_filtering_pct": 100,
                "rows_for_plan": 1,
                "cost_for_plan": 0.71,
                "chosen": true
              }
            ]
          },
          {
            "attaching_conditions_to_tables": {
              "original_condition": "((`s1`.`common_field` = 'abc') and (`s1`.`key1` > 'z') and (`s1`.`key2` < 1000000) and (`s1`.`key3` in ('aa','bb','cb')))",
              "attached_conditions_computation": [
              ],
              "attached_conditions_summary": [
                {
                  "table": "`s1`",
                  "attached": "((`s1`.`common_field` = 'abc') and (`s1`.`key1` > 'z') and (`s1`.`key2` < 1000000) and (`s1`.`key3` in ('aa','bb','cb')))"
                }
              ]
            }
          },
          {
          # 尝试给查询添加一些其他的查询条件
            "finalizing_table_conditions": [
              {
                "table": "`s1`",
                "original_table_condition": "((`s1`.`common_field` = 'abc') and (`s1`.`key1` > 'z') and (`s1`.`key2` < 1000000) and (`s1`.`key3` in ('aa','bb','cb')))",
                "final_table_condition   ": "((`s1`.`common_field` = 'abc') and (`s1`.`key1` > 'z') and (`s1`.`key2` < 1000000) and (`s1`.`key3` in ('aa','bb','cb')))"
              }
            ]
          },
          {
           # 再稍稍的改进一下执行计划
            "refine_plan": [
              {
                "table": "`s1`",
                "pushed_index_condition": "(`s1`.`key1` > 'z')",
                "table_condition_attached": "((`s1`.`common_field` = 'abc') and (`s1`.`key2` < 1000000) and (`s1`.`key3` in ('aa','bb','cb')))"
              }
            ]
          }
        ]
      }
    },
    {
      "join_execution": { # execute阶段
        "select#": 1,
        "steps": [
        ]
      }
    }
  ]
}
# 因优化过程文本太多而丢弃的文本字节大小,值为0时表示并没有丢弃
MISSING_BYTES_BEYOND_MAX_MEM_SIZE: 0
# 权限字段
INSUFFICIENT_PRIVILEGES: 0
1 row in set (0.01 sec)

ERROR: 
No query specified

Cuando vea este resultado, su primera impresión es que este texto es demasiado. De hecho, esto es solo una pequeña parte del proceso de ejecución del optimizador. MySQL puede agregar más información sobre el proceso de optimización en versiones posteriores. Sin embargo, el caos es bastante regular.El proceso de optimización se divide aproximadamente en tres etapas:

  • preparar el escenario
  • optimizar la etapa
  • etapa de ejecución

La optimización basada en costos de la que estamos hablando se enfoca principalmente en optimizela etapa. Para la consulta de una sola tabla, nos enfocamos principalmente en este proceso optimizede la etapa "rows_estimation". Este proceso analiza profundamente el costo de varios esquemas de ejecución para la consulta de una sola tabla; join query En general, debemos prestar más atención a "considered_execution_plans"este proceso, que describirá los costos correspondientes a varios métodos de conexión. De todos modos, el optimizador eventualmente elegirá el plan con el costo más bajo como el plan de ejecución final, que es el EXPLAINtipo de plan que usamos para mostrar.

Si algunos amigos EXPLAINno entienden el plan de ejecución de una determinada consulta que se muestra en la declaración, puede intentar usar optimizer tracela función para obtener más información sobre el costo correspondiente a cada plan de ejecución. Creo que esta función puede brindarle una comprensión más profunda de las consultas de MySQL. .optimizador

Hasta ahora, el estudio de hoy ha terminado, espero que te conviertas en un yo indestructible
~~~

No puedes conectar los puntos mirando hacia adelante; solo puedes conectarlos mirando hacia atrás. Así que tienes que confiar en que los puntos se conectarán de alguna manera en tu futuro. Tienes que confiar en algo: tu instinto, destino, vida, karma, lo que sea. Este enfoque nunca me ha defraudado y ha marcado una gran diferencia en mi vida.

Si mi contenido te es útil, por favor 点赞, 评论,, 收藏la creación no es fácil, el apoyo de todos es la motivación para que yo persevere

inserte la descripción de la imagen aquí

Supongo que te gusta

Origin blog.csdn.net/liang921119/article/details/130865093
Recomendado
Clasificación