Este artículo lo lleva a comprender la optimización basada en costos de MySQL

prefacio

Solíamos decir que MySQL puede tener diferentes esquemas de ejecución para ejecutar una consulta, y elegirá el esquema con el costo más bajo o el costo más bajo para ejecutar la consulta ¿Cómo puedo llevarlo a entenderlo en detalle?

1. ¿Cuál es el costo?

Solíamos decir que MySQL puede tener diferentes planes de ejecución para ejecutar una consulta, y elegirá uno de ellos 成本最低, o 代价最低ese plan, para ejecutar la consulta. Sin embargo, nuestra descripción previa del costo es muy vaga, de hecho, el costo de ejecución de una declaración de consulta en MySQL se compone de los siguientes dos aspectos:

  • I/O成本

    Los motores de almacenamiento MyISAM e InnoDB que suelen utilizar nuestras tablas almacenan tanto datos como índices en el disco.Cuando queremos consultar los registros de la tabla, necesitamos cargar los datos o índices en la memoria antes de operar. El tiempo perdido en el proceso de carga del disco a la memoria se denomina costo de E/S.

  • CPU成本

    El tiempo consumido por operaciones como leer y verificar si los registros cumplen con las condiciones de búsqueda correspondientes y clasificar el conjunto de resultados se denomina costo de CPU.

Para el motor de almacenamiento InnoDB, una página es la unidad básica de interacción entre el disco y la memoria. MySQL estipula que el costo predeterminado para leer una página es 0.25(predeterminado de MySQL 5.7 1.0), y el costo predeterminado para leer y verificar si un registro cumple con la búsqueda el criterio es 0.1(predeterminado de MySQL 5.7 0.2). 0.25Y 0.1estos números se llaman 成本常数, estas dos constantes de costo son las más utilizadas, y hablaremos del resto de las constantes de costo más adelante.

Sugerencia:
Cabe señalar que el costo es de 0,1 independientemente de si se necesita verificar si se cumple la condición de búsqueda al leer el registro.
La versión de MySQL utilizada aquí es la 8.0.32 y el costo variará entre las versiones. Se explica en detalle más adelante en este capítulo.

2. El costo de la consulta de una sola tabla

2.1 Preparar datos

Para nuestro estudio normal, todavía usamos el anterior demo8. Me temo que todos olvidarán cómo es este reloj, así que les copio un artículo:

mysql> USE testdb;

mysql> create table demo8 (    
id int not null auto_increment,    
key1 varchar(100),    
key2 int,    
key3 varchar(100),    
key_part1 varchar(100),    
key_part2 varchar(100),    
key_part3 varchar(100),    
common_field varchar(100), 
primary key (id),
key idx_key1 (key1),    
unique key idx_key2 (key2),    
key idx_key3 (key3),    
key idx_key_part(key_part1, key_part2, key_part3));

Se ha creado un total de 1 índice agrupado (clave principal) y 4 índices secundarios para la tabla demo8:

  • idEl índice agrupado creado para la columna;
  • key1Un índice secundario creado para la columna;
  • key2Un índice secundario único creado para la columna;
  • key3Un índice secundario creado para la columna;
  • Un índice secundario compuesto (conjunto) creado para las columnas key_part1, key_part2, .key_part3

Luego, debemos insertar 20000un registro para esta tabla e insertar valores aleatorios en las otras columnas, excepto en la columna de identificación.

mysql> delimiter //
create procedure demo8data()
begin    
	declare i int;    
	set i=0;    
	while i<20000 do        
		insert into demo8(key1,key2,key3,key_part1,key_part2,key_part3,common_field) values(
		substring(md5(rand()),1,2),
		i+1,
		substring(md5(rand()),1,3),
		substring(md5(rand()),1,4),
		substring(md5(rand()),1,5),
		substring(md5(rand()),1,6),
		substring(md5(rand()),1,7)
		);        
		set i=i+1;    
	end while;
end;
//
delimiter ;

mysql> call demo8data();

Comencemos oficialmente nuestro estudio.

2.2 Pasos de optimización basados ​​en costos

Antes de que se ejecute realmente una declaración de consulta de una sola tabla, el optimizador de consultas de MySQL encontrará todas las soluciones posibles para ejecutar la declaración y, después de la comparación, encontrará la solución con el costo más bajo. Esta solución con el costo más bajo es la llamada ejecución plan, y luego llamará a la interfaz proporcionada por el motor de almacenamiento para ejecutar realmente la consulta. El resumen de este proceso es el siguiente:

  • Encuentre todos los índices posibles para usar en función de los criterios de búsqueda.
  • Calcule el costo de un escaneo completo de la tabla
  • Calcule el costo de ejecutar una consulta usando diferentes índices
  • Compare los costos de varios planes de ejecución para encontrar el que tenga el costo más bajo

A continuación, usamos un ejemplo para analizar estos pasos. La declaración de consulta de una sola tabla es la siguiente:

mysql> select * from demo8 where
	key1 in ('aa','bb','cc') and
	key2 > 10 and key2 < 1000 and
	key3 > key2 and
	key_part1 like '%3f%' and
	common_field='1281259';

Se ve complicado, analicémoslo paso a paso

Paso 1: encuentre todos los posibles índices aplicables de acuerdo con los criterios de búsqueda

Como dijimos antes, para el índice de árbol B+, siempre que la columna de índice y la constante estén conectadas usando =, <=>, IN, , NOT IN, IS NULL, IS NOT NULL, >, <>=, <=, BETWEEN AND, !=(no igual a también se puede escribir <>) u LIKEoperadores, así -llamado El intervalo de rango (LIKE coincide con el prefijo de cadena también está bien), es decir, estas condiciones de búsqueda pueden usar índices, y MySQL llama a los índices que pueden usarse en una consulta possible keys.

Analicemos varias condiciones de autorización involucradas en la consulta anterior:

  • key1 in ('aa','bb','cc'), esta condición de búsqueda puede usar el índice secundarioidx_key1
  • key2 > 10 and key2 < 1000, esta condición de búsqueda puede usar el índice secundarioidx_key2
  • key3 > key2, debido a que la columna de búsqueda de esta condición de búsqueda no se compara con una constante, no se puede utilizar el índice.
  • key_part1 like '%3f%', key_part1use likeoperadores para comparar cadenas que comiencen con comodines, y no se pueden usar índices
  • common_field=‘1281259’, dado que la columna no tiene ningún índice, el índice no se utilizará

En resumen, los índices que se pueden usar en la declaración de consulta anterior son solo possible keyssum idx_key1e index idx_key2.

Paso 2: Calcule el costo del escaneo completo de la tabla

Para el motor de almacenamiento InnoDB, el escaneo completo de la tabla significa comparar los registros en el índice agrupado con las condiciones de búsqueda dadas a su vez, y agregar los registros que cumplen con las condiciones de búsqueda al conjunto de resultados, por lo que el índice agrupado debe ser correspondiente La página se carga en la memoria y luego se verifica para ver si el registro coincide con los criterios de búsqueda. Dado que 查询成本=I/O成本+CPU成本, calcular el costo de un escaneo completo de la tabla requiere dos datos:

  • 聚簇索引占用的页面数
  • 该表中的记录数

¿De dónde vienen estos dos datos? MySQL mantiene una serie de información estadística para cada tabla, explicaremos en detalle cómo se recopila esta información estadística más adelante en este capítulo, ahora veamos cómo visualizar esta información estadística. MySQL nos proporciona declaraciones para ver la información estadística de una tabla, si queremos ver la información estadística de una tabla específica, basta con agregar la declaración correspondiente show table statusdespués de la declaración, por ejemplo, si queremos ver la información estadística de esta tabla , podemos escribirlo así:
likedemo8

mysql> show table status like 'demo8' \G;
*************************** 1. row ***************************
           Name: demo8
         Engine: InnoDB
        Version: 10
     Row_format: Dynamic
           Rows: 20187
 Avg_row_length: 78
    Data_length: 1589248
Max_data_length: 0
   Index_length: 2785280
      Data_free: 4194304
 Auto_increment: 20001
    Create_time: 2023-05-16 16:36:53
    Update_time: 2023-05-16 16:38:21
     Check_time: NULL
      Collation: utf8mb4_0900_ai_ci
       Checksum: NULL
 Create_options: 
        Comment: 
1 row in set (0.00 sec)

ERROR: 
No query specified

Si bien surgen muchas opciones de estadísticas, solo nos importan dos por ahora:

  • Rows: Esta opción indica el número de registros en la tabla. Para MyISAMlas tablas que usan motores de almacenamiento, este valor es exacto y para InnoDBlas tablas que usan motores de almacenamiento, este valor es una estimación. Como se puede ver en los resultados de la consulta, dado que nuestra demo8tabla usa InnoDBun motor de almacenamiento, aunque en realidad hay 20 000 registros en la tabla, el valor show table statusmostrado Rowses 20 187 registros.

  • Data_length: Esta opción indica el número de bytes de espacio de almacenamiento ocupado. Para una tabla que usa MyISAMun motor de almacenamiento, este valor es el tamaño del archivo de datos Para una InnoDBtabla que usa un motor de almacenamiento, este valor es equivalente al tamaño del espacio de almacenamiento ocupado por el índice agrupado

    Es decir, el tamaño del valor se puede calcular así:

    Data_length = 聚簇索引的页面数量 * 每个页面的大小

    Nuestra demo8tabla usa el 16KBtamaño de página predeterminado, de acuerdo con los resultados de la consulta anterior, por lo que podemos calcular el número de páginas en el índice agrupado:

    聚簇索引的页面数=1589248 ÷ 16 ÷ 1024 = 97

Ahora que hemos obtenido el número estimado de páginas ocupadas por el índice agrupado y el número de registros en la tabla, ahora podemos ver el proceso de cálculo del escaneo completo de la tabla:

  • I/OCosto: 97 * 0.25 = 24.25
    97se refiere a la cantidad de páginas ocupadas por el índice agrupado, 0.25se refiere a la constante de costo de cargar una página
  • CPUCosto: 20187 * 0.1 = 2018.7
    20187se refiere a la cantidad de registros en la tabla en los datos estadísticos, que InnoDBes un valor estimado para el motor de almacenamiento, 0.1y se refiere a la constante de costo requerida para acceder a un registro
  • 总成本:24,25 +2018,7 =2042.95

En resumen, demo8el costo total requerido para el escaneo completo de la tabla es 2042.95cargar directamente el código, sin tonterías, y una verificación magnífica.

mysql> explain format=json select * from demo8 ;

inserte la descripción de la imagen aquí

小提示:
Dijimos anteriormente que los registros en la tabla en realidad se almacenan en los nodos de hoja del índice agrupado correspondiente al árbol B+, por lo que siempre que obtengamos el nodo de hoja más a la izquierda a través del nodo raíz, podemos seguir la lista doblemente enlazada compuesta por nodos hoja Compruébalos todos. Es decir, en el proceso de escaneo completo de la tabla, no es necesario acceder a algunos nodos en el árbol B+, pero MySQL usa directamente el número de páginas ocupadas por el índice agrupado como base para calcular el costo de E/S cuando calcular el costo del escaneo completo de la tabla La distinción entre nodos internos y nodos de hoja es un poco simplista, solo préstele atención.

Paso 3: Calcule el costo de la consulta ejecutada por diferentes índices

Del análisis del paso 1, obtenemos que la consulta anterior puede usar idx_key1estos idx_key2dos índices. Necesitamos analizar el costo de usar estos índices solos para ejecutar la consulta y, finalmente, analizar si es posible usar la combinación de índices. Lo que debe mencionarse aquí es que el optimizador de consultas MySQL primero analiza el costo de usar un índice secundario único y luego analiza el costo de usar un índice común, por lo que también analizamos el costo primero y luego observamos el costo de idx_key2uso idx_key1. .

Coste de las consultas realizadas con idx_key2

idx_key2La condición de búsqueda correspondiente es: key2 > 10 and key2 < 1000, es decir, el intervalo de rango correspondiente es: ( 10, 1000), y idx_key2el diagrama de búsqueda es el siguiente:

inserte la descripción de la imagen aquí

Para 二级索引+回表la consulta del método, MySQL calcula el costo de esta consulta que depende de dos aspectos de los datos:

  • 范围区间的数量:No importa cuántas páginas ocupe el índice secundario en un cierto rango, el optimizador de consultas lo considera aproximadamente 读取索引的一个范围区间的I/O成本和读取一个页面是相同的. En este ejemplo, solo hay un rango usando idx_key2: ( 10,1000), por lo que el equivalente de acceder al índice secundario de este rango I/O成本es:1 * 0.25 = 0.25

  • 需要回表的记录数:El optimizador necesita calcular cuántos registros están contenidos en un cierto rango del índice secundario. Para la relación, es necesario calcular 10,1000cuántos registros del índice secundario contiene idx_key2 en el rango ( ). El proceso de cálculo es el siguiente:

    • Paso 1: Primero key2 > 10visite idx_key2el índice de árbol B+ correspondiente de acuerdo con esta condición, y busque key2 > 10el primer registro que cumpla con esta condición. Llamamos a este registro el registro más a la izquierda del intervalo. Dijimos anteriormente que el proceso de ubicar un registro en el árbol de números B+ es extremadamente rápido y constante, por lo que el consumo de rendimiento de este proceso es insignificante.

    • Paso 2: Luego continúe buscando el primer registro que cumpla con esta condición key2 < 1000del idx_key2índice de árbol B+ correspondiente según esta condición Llamamos a este registro el registro más a la derecha en el intervalo, y el consumo de rendimiento de este proceso también es insignificante.

    • Paso 3: si el intervalo entre el registro más a la izquierda y el registro más a la derecha en el intervalo no está demasiado lejos (en la versión de MySQL 5.7.21, siempre que el intervalo no sea inferior a 10 páginas), puede contar con precisión el índices secundarios que cumplen las key2> 10 AND key2 < 1000condiciones El número de registros. De lo contrario, simplemente lea 10 páginas a la derecha a lo largo del registro más a la izquierda en el intervalo, calcule el número promedio de registros contenidos en cada página y luego multiplique este promedio por el número de páginas entre el registro más a la izquierda y el registro más a la derecha en el intervalo. Entonces surge nuevamente la pregunta, ¿cómo estimar cuántas páginas hay entre el registro más a la izquierda en el intervalo y el registro más a la derecha en el intervalo? Para resolver este problema, tenemos que volver a la estructura del índice del árbol B+:

      inserte la descripción de la imagen aquí

    • Como se muestra en la figura, por ejemplo, el registro más a la izquierda en el intervalo está en la página 34, y el registro más a la derecha en el intervalo está en la página 40. Luego quiero calcular el número de páginas entre el registro más a la izquierda en el intervalo y el registro más a la derecha en el intervalo, lo que equivale a calcular el número entre la página 34 y la página 40. ¿Cuántas páginas hay? Cada registro de entrada de directorio corresponde a una página de datos, por lo que calcular el número de páginas entre la página 34 y la página 41 es equivalente a calcular la distancia entre los registros de entrada de directorio correspondientes en sus nodos principales (es decir, página 42) ¿No sería suficiente tener algunos registros? Si hay demasiadas páginas antes de la página 34 y la página 41 (los elementos del directorio correspondientes no están en la misma página del nodo principal), continúe contando recursivamente. Este proceso estadístico se lleva a cabo en la página del nodo principal. Dijimos antes que un El árbol B+ tiene 4. La altura de la capa ya es muy alta, por lo que no consume mucho rendimiento.

Después de saber cómo contar el número de registros en un cierto rango del índice secundario, es necesario volver al problema real, según el algoritmo anterior, hay alrededor de un registro idx_key2en el intervalo ( ). El costo de la CPU para leer este registro de índice secundario es:10, 1000989989989 * 0.1 = 98.9

¿Dónde 989está el número de registros de índice secundario que deben leerse 0.1y es la constante de costo para leer un registro?

Después de obtener los registros a través del índice secundario, se deben hacer dos cosas más:

  • De acuerdo con el valor de la clave principal en estos registros, regrese al índice agrupado para realizar operaciones de tabla

    Aquí hay que echar un vistazo más de cerca, la evaluación de MySQL del costo de E/S de la operación de retorno de tabla sigue siendo muy atrevida, ellos piensan que cada operación de retorno de tabla es equivalente a acceder a una página, es decir, cuántos registros hay están en el rango del índice secundario Cuántas veces volver a la tabla, es decir, cuántas E/S de página se deben realizar. De acuerdo con las estadísticas anteriores, cuando se utiliza el índice secundario idx_key2 para realizar consultas, se estima que se deben devolver a la tabla 989 registros de índice secundario. El costo de E/S causado por la operación de devolución de la tabla es:989 x 0.25 = 247.25

    donde 989es el número esperado de registros de índice secundario y 0.25es una constante para el costo de E/S de una página.

  • El registro completo de la cuenta obtenido después de regresar a la tabla y luego verificar si otras condiciones de búsqueda son verdaderas

    La esencia de la operación de retorno de la tabla es encontrar el registro de usuario completo en el índice agrupado a través del valor de la clave principal del registro del índice secundario y luego verificar si las condiciones de búsqueda key2 > 10 and key2 < 1000distintas a esta condición de búsqueda son verdaderas. Debido a que obtuvimos un total de 989 registros de índice secundario a través del intervalo de rango, que corresponde a un registro de usuario completo en el índice agrupado 989, el costo de CPU de leer y verificar si estos registros de usuario completos cumplen con el resto de las condiciones de búsqueda es el siguiente:989 x 0.1 = 98.9

    Entre ellos, 989 es el número de registros a detectar, 0.1que es la constante de costo para detectar si un registro cumple con las condiciones de búsqueda dadas.

Entonces, el costo de ejecutar una consulta usando idx_key2 en este ejemplo es el siguiente:

  • I/O成本: 1.0x 0.25 + 989 x 0.25 = 247.5(número de intervalos de rango + número estimado de registros de índice secundario)

  • CPU成本: 989 x 0.1 + 0.01 + 989 x 0.1 = 197.81(El costo de leer los registros del índice secundario + el costo de leer y detectar los registros del índice agrupado después de regresar a la tabla)

En resumen, el costo total de usar idx_key2 para ejecutar la consulta es: 247.5 + 197.81 = 445.31, cargue directamente el código, sin tonterías, una verificación hermosa:

mysql> explain format=json select * from demo8 where key2 > 10 and key2 < 1000 and key3 > key2 and key_part1 like '%3f%' and common_field='1281259';

inserte la descripción de la imagen aquí

小提示:
Si usa el índice, hay un ajuste fino de las condiciones para leer el índice secundario, pero no para leer el índice agrupado. Cualquier registro en el intervalo de escaneo y de regreso a la tabla es equivalente a leer una página. Si no se utiliza el índice, se analizará por separado el valor de ajuste fino, que es diferente al anterior.

Coste de las consultas realizadas con idx_key2

idx_key1La condición de búsqueda correspondiente key1 in ('aa','bb','cc')también es equivalente a tres intervalos de un solo punto:

  • ['aa','aa']
  • ['bb','bb']
  • ['cc','cc']

El diagrama esquemático del uso idx_key1de la búsqueda es el siguiente:

inserte la descripción de la imagen aquí

Similar al idx_key2caso de uso, también necesitamos idx_key1la cantidad de intervalos de rango a los que se debe acceder y la cantidad de registros que se deben devolver a la tabla.

  • 范围区间数量idx_key1: Obviamente, hay tres intervalos de un solo punto cuando se usa la consulta, por lo que I/Oel costo de acceder al índice secundario de estos tres intervalos de rango es3 x 0.25 = 0.75
  • 需要回表的记录数
    • ['aa','aa']Buscar el número de registros de índice secundario correspondientes a un intervalo de un solo punto es lo mismo que buscar el número de registros de índice secundario correspondientes a un intervalo de rango continuo. Tanto el registro más a la izquierda como el registro más a la derecha del intervalo se calculan primero, y luego se calcula la cantidad de registros entre ellos . ['aa','aa']registro del índice secundario del intervalo de un solo punto es:67
    • ['bb','bb']El registro de índice secundario correspondiente a la búsqueda de intervalo de punto único es:88
    • ['cc','cc']El registro de índice secundario correspondiente a la búsqueda de intervalo de punto único es:75

Por lo tanto, el número total de registros que deben devolverse a la tabla para estos tres intervalos de un solo punto es: 67+88+75 = 230y el costo de leer estos registros de índice secundario CPUes:230 x 0.1 + 0.01 = 23.01

Después de obtener el número total de registros que deben devolverse a la tabla, considere:

  • De acuerdo con el valor de la clave principal en estos registros, la operación de la tabla se realiza en el índice agrupado y el I/Ocosto requerido es:230 x 0.25 = 57.5
  • Después de regresar a la tabla, se obtiene el registro completo del usuario, y CPUel costo correspondiente a este paso de comparar si otras condiciones de búsqueda son verdaderas es:230 x 0.1 = 23

Entonces, el costo de ejecutar una consulta usando idx_key1 en este ejemplo es el siguiente:

  • I/O成本:0.75 + 57.5 =58.25
  • CPU成本:23 + 23.01 =46.01

En resumen, el costo total de ejecutar una consulta usando idx_key1 es: 58.25 + 46.01 = 104.26, cargue directamente el código, sin tonterías, una verificación hermosa:

mysql> explain format=json select * from demo8 where key1 in ('aa','bb','cc') and key2 > 10 and key2 < 1000 and key3 > key2 and key_part1 like '%3f%' and common_field='1281259';

inserte la descripción de la imagen aquí
¿Es posible usar Index Merge?

En este ejemplo, las condiciones de búsqueda key1para y están conectadas mediante concatenación, mientras que para y son consultas de rango, es decir, los registros de índice no agrupados encontrados no están ordenados según el valor de la clave principal y no cumplen las condiciones. para usar la combinación de índices, por lo que no se usará ninguna combinación de índices.key2ANDidx_key1idk_key2Intersection

小提示:
El algoritmo utilizado por el optimizador de consultas de MySQL para calcular el costo de la fusión de índices también es engorroso. No hablaré de eso aquí. Solo comprenda cómo se calcula el costo y sepa que MySQL seleccionará el índice de acuerdo con este algoritmo.

Paso 4: Compare los costos de varios planes de ejecución y encuentre el que tenga el costo más bajo

Los diversos esquemas ejecutables para ejecutar la consulta en este ejemplo y sus costos correspondientes se enumeran a continuación:

  • 全表扫描el costo de:2042.95
  • idx_key2Costos utilizados :445.31
  • idx_key1Costos utilizados :104.26

Obviamente, idx_key1el costo más bajo para usar, por lo que, por supuesto, elija idx_key1ejecutar la consulta.

2.3 Cálculo de costos basado en estadísticas de índices

A veces hay muchos intervalos de un solo punto cuando se usa un índice para ejecutar una consulta. Por ejemplo, usar la declaración IN puede generar fácilmente muchos intervalos de un solo punto, como la consulta a continuación (el ... en la declaración de consulta a continuación indica que hay muchos parámetros):

select * from demo8 where key1 in ('aa', 'bb', 'cc', ... , 'ee');

Obviamente, el índice que se puede utilizar en esta consulta es idx_key1. Dado que este índice no es el único índice secundario, no es posible determinar el número de registros de índice secundario correspondientes a un intervalo de un solo punto. Necesitamos calcularlo. El método de cálculo se ha introducido anteriormente, que consiste en obtener primero el registro más a la izquierda y el registro más a la derecha del intervalo del árbol B+ correspondiente al índice, y luego calcular cuántos registros hay entre estos dos registros (se puede hacer cuando el número de registros es pequeño) Cálculo preciso, a veces solo estimaciones). MySQL llama a este método de calcular el número de registros de índice correspondientes a un determinado intervalo de rango accediendo directamente al árbol B+ correspondiente al índice index dive.

小提示:
La traducción literal de sumergirse al chino significa zambullirse y descender en picado. Perdona mi inglés. buceo de índice, buceo de índice? ¿Deslizamiento del índice? No parece ser adecuado, así que no lo traduciré en absoluto. Sin embargo, todos deben entender que la inmersión de índice es usar directamente el árbol B+ correspondiente al índice para calcular la cantidad de registros correspondientes a un cierto rango.

Si hay varios intervalos de un solo punto, no es un problema usar el método de inmersión de índice para calcular la cantidad de registros correspondientes a estos intervalos de un solo punto, pero no puede soportar que algunos amigos intenten meter cosas en la declaración IN Si hay 20 000 registros en el parámetro de declaración IN, significa que el optimizador de consultas de MySQL necesita realizar 20 000 operaciones de inmersión de índice para calcular la cantidad de registros de índice correspondientes a estos intervalos de un solo punto. registros es más alto que el costo del escaneo directo de la tabla completa. Por supuesto, MySQL ha considerado esta situación, por lo que proporciona una variable del sistema eq_range_index_dive_limit. Echemos un vistazo al valor predeterminado de esta variable del sistema:

mysql> show variables like '%dive%';
+---------------------------+-------+
| Variable_name             | Value |
+---------------------------+-------+
| eq_range_index_dive_limit | 200   |
+---------------------------+-------+
1 row in set (0.00 sec)

Es decir, si el número de parámetros en nuestra declaración IN es menor a 200, se usará index dive para calcular el número de registros correspondientes a cada intervalo de un solo punto, si es mayor o igual a 200, index dive no se puede utilizar. , se estima utilizando las llamadas estadísticas de índice. ¿Qué tipo de estimación? sigue leyendo

MySQL mantendrá datos estadísticos para cada tabla, y MySQL también mantendrá datos estadísticos para cada índice en la tabla.La sintaxis que se puede usar para ver los datos estadísticos de un índice en una tabla, por ejemplo, miramos show index from 表名cada demo8índice Las estadísticas se pueden escribir así:

mysql> show index from demo8;
+-------+------------+--------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| Table | Non_unique | Key_name     | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | Visible | Expression |
+-------+------------+--------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| demo8 |          0 | PRIMARY      |            1 | id          | A         |       18750 |     NULL |   NULL |      | BTREE      |         |               | YES     | NULL       |
| demo8 |          0 | idx_key2     |            1 | key2        | A         |       18565 |     NULL |   NULL | YES  | BTREE      |         |               | YES     | NULL       |
| demo8 |          1 | idx_key1     |            1 | key1        | A         |         256 |     NULL |   NULL | YES  | BTREE      |         |               | YES     | NULL       |
| demo8 |          1 | idx_key3     |            1 | key3        | A         |        4053 |     NULL |   NULL | YES  | BTREE      |         |               | YES     | NULL       |
| demo8 |          1 | idx_key_part |            1 | key_part1   | A         |       16122 |     NULL |   NULL | YES  | BTREE      |         |               | YES     | NULL       |
| demo8 |          1 | idx_key_part |            2 | key_part2   | A         |       18570 |     NULL |   NULL | YES  | BTREE      |         |               | YES     | NULL       |
| demo8 |          1 | idx_key_part |            3 | key_part3   | A         |       18570 |     NULL |   NULL | YES  | BTREE      |         |               | YES     | NULL       |
+-------+------------+--------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
7 rows in set (0.02 sec)

Hay muchos atributos, pero estos atributos no son difíciles de entender, presentaremos brevemente estos atributos aquí:

Nombre del Atributo describir
Mesa El nombre de la tabla a la que pertenece el índice.
No es único Si el valor de la columna de índice es único, el valor de la columna del índice agrupado y el índice secundario único es 0, y el valor de la columna del índice secundario ordinario es 1
Nombre clave nombre de índice
Seq_in_index La posición de la columna de índice en el índice, contando desde 1. Por ejemplo, para el índice conjunto idx_key_part, las posiciones correspondientes de key_part1, key_part2 y key_part3 son 1, 2 y 3 respectivamente.
nombre_columna el nombre de la columna de índice
Colación El método de clasificación para almacenar los valores en la columna de índice. Cuando el valor es A, significa almacenar en orden ascendente, y cuando es NULL, significa almacenar en orden descendente
Cardinalidad El número de valores únicos en la columna de índice. Nos centraremos en esta propiedad más adelante.
Sub_parte Para las columnas que almacenan cadenas o cadenas de bytes, a veces solo queremos indexar los primeros n caracteres o bytes de estas cadenas, y este atributo representa el valor n. Si se indexa la columna completa, el valor de esta propiedad es NULL
Lleno Cómo se comprime la columna de índice, el valor NULL significa que no está comprimido. No entendemos este atributo por el momento, por lo que podemos ignorarlo primero.
Nulo Si la columna de índice permite almacenar valores NULL.
tipo_índice El tipo de índice utilizado, el más utilizado es BTREE, que en realidad es el índice de árbol B+
Comentario Información de comentario de columna de índice
Índice_comentario Información de la anotación del índice

PackedExcepto que es posible que no entiendas los atributos anteriores , no debería haber nada que no puedas entender. Si hay alguno, debes haberlos saltado cuando leíste el artículo anterior. De hecho, lo que más nos preocupa ahora es el atributo Cardinality, Cardinalityque literalmente 基数significa la cantidad de valores únicos en la columna de índice. Por ejemplo, para una tabla con 10.000 registros, el atributo de Cardinalidad de una columna de índice es 10000, lo que significa que no hay valores duplicados en la columna.Si el atributo de Cardinalidad es 1, significa que todos los valores de la columna son duplicados. Sin embargo, debe tenerse en cuenta que para el motor de almacenamiento InnoDB, el show indexatributo de cardinalidad de una columna de índice que muestra la declaración es uno 估计值y no es preciso. Hablemos más adelante de cómo se calcula el valor de este atributo de Cardinalidad, veamos para qué se usa.

>=Como se mencionó anteriormente, cuando el valor de la variable del sistema de número de parámetro en la instrucción IN eq_range_index_dive_limit, el método no se utilizará index divepara calcular el número de registros de índice correspondientes a cada intervalo de un solo punto, pero los datos de estadísticas de índice, los datos de estadísticas de índice referidos hasta aquí Hace referencia a estos dos valores:

  • Use el valor show table statusque muestra la declaración Rows, es decir, cuántos registros hay en una tabla

  • Usando las propiedades show indexmostradas por la declaraciónCardinality

En combinación con las estadísticas de filas anteriores, podemos calcular el número promedio de veces que se repite un valor para la columna de índice. 一个值的重复次数≈Rows÷Cardinality, tomando como ejemplo el índice demo8de la tabla , su valor de Filas es , que corresponde al valor de la columna índice clave1 , por lo que podemos calcular el número de repeticiones del valor único promedio de la columna clave1:idx_key120187Cardinality25620187÷256≈79

Ahora mire la declaración de consulta anterior:

select * from demo8 where key1 in ('aa', 'bb', 'cc', ... , 'ee');

Suponiendo que hay un parámetro en la declaración IN 20000, los datos estadísticos se usan directamente para estimar el número de registros correspondientes al rango de un solo punto para estos parámetros. Cada parámetro corresponde a aproximadamente 79un registro, por lo que el número total de registros que necesitan que se devuelve a la mesa es:20000 x 79 = 1580000

El uso de datos estadísticos para calcular la cantidad de registros de índice correspondientes a un intervalo de un solo punto index divees mucho más simple, pero su debilidad fatal es que el 不精确!costo de consulta calculado con datos estadísticos puede ser muy diferente del costo real.

小提示:
Cuando se usa la consulta IN en su consulta, pero el índice no se usa realmente, debe considerar si el valor de eq_range_index_dive_limit es demasiado pequeño

3. El costo de la consulta de conexión

2.1 Preparar datos

La consulta de conexión requiere al menos dos tablas, y solo una tabla demo8 no es suficiente, por lo que para el buen desarrollo de la historia, construimos directamente dos tablas s1 y s2 que son exactamente iguales a la tabla demo8

mysql> create table s1 (    
id int not null auto_increment,    
key1 varchar(100),    
key2 int,    
key3 varchar(100),    
key_part1 varchar(100),    
key_part2 varchar(100),    
key_part3 varchar(100),    
common_field varchar(100), 
primary key (id),
key idx_key1 (key1),    
unique key idx_key2 (key2),    
key idx_key3 (key3),    
key idx_key_part(key_part1, key_part2, key_part3));
Query OK, 0 rows affected (0.04 sec)

mysql> create table s2 (    
id int not null auto_increment,    
key1 varchar(100),    
key2 int,    
key3 varchar(100),    
key_part1 varchar(100),    
key_part2 varchar(100),    
key_part3 varchar(100),    
common_field varchar(100), 
primary key (id),
key idx_key1 (key1),    
unique key idx_key2 (key2),    
key idx_key3 (key3),    
key idx_key_part(key_part1, key_part2, key_part3));
Query OK, 0 rows affected (0.04 sec)

mysql> insert into s1 select * from demo8;
Query OK, 20000 rows affected (0.83 sec)
Records: 20000  Duplicates: 0  Warnings: 0

mysql> insert into s2 select * from demo8;
Query OK, 20000 rows affected (0.89 sec)
Records: 20000  Duplicates: 0  Warnings: 0

2.2 Introducción al filtrado de condiciones

Como dijimos antes, la consulta de combinación en MySQL utiliza el algoritmo de combinación de bucle anidado, se accederá a la tabla de control una vez y se puede acceder a la tabla controlada varias veces, por lo que para la consulta de combinación de dos tablas, el costo de la consulta es el siguiente Consta de dos partes:

  • El costo de una sola consulta que maneja la tabla
  • El costo de consultar la tabla impulsada varias veces ( 具体查询多少次取决于对驱动表查询的结果集中有多少条记录)

A la cantidad de registros obtenidos después de consultar la tabla de conducción la llamamos tabla de conducción 扇出(nombre en inglés: fanout). Obviamente, cuanto menor sea el valor de despliegue de la tabla de control, menor será el número de consultas a la tabla controlada y menor el costo total de la consulta de conexión. Cuando el optimizador de consultas quiere calcular el costo de toda la consulta de combinación, necesita calcular el valor de despliegue de la tabla de control. A veces, el cálculo del valor de despliegue es muy fácil, como las siguientes dos consultas:

Consulta uno:

select * from s1 inner join s2;

Suponiendo que s1la tabla se utilice como tabla de control, es obvio que la consulta de una sola tabla de la tabla de control solo se puede realizar mediante el escaneo completo de la tabla, y el valor de despliegue de la tabla de control también es muy claro, es decir , cuántos registros hay en la tabla de control, el valor de despliegue es el número . El número de registros en la tabla s1 en los datos estadísticos es sí 20250, es decir, el optimizador 20250lo considerará directamente como el valor de distribución en s1la tabla.

Consulta dos:

select * from s1 inner join s2 where s1.key2 > 10 and s1.key2 < 1000;

Aún suponiendo que la tabla s1 sea la tabla de control, es obvio que las consultas de una sola tabla en la tabla de control pueden usar el índice idx_key2 para realizar consultas. En este momento, ¿cuántos registros hay en el intervalo de rango (10, 1000) de idx_key2? Entonces, ¿cuál es el valor de despliegue? Calculamos anteriormente que la cantidad de registros que satisfacen el intervalo de rango (10, 1000) de idx_key2 es 989, lo que significa que en esta consulta, el optimizador considerará 95 como el valor de despliegue de la tabla de control s1.

Por supuesto, las cosas no siempre salen bien, o la trama sería demasiado plana. A veces, el cálculo del valor de fan-out se vuelve complicado, como la siguiente consulta:

Consulta tres:

select * from s1 inner join s2 where s1.common_field > 'xyz'

common_field > 'xyz'Esta consulta es similar a la consulta 1, excepto que hay una condición de búsqueda más para la tabla de control s1 . El optimizador de consultas en realidad no ejecutará la consulta, entonces, ¿cuántos registros en 只能猜este registro satisfacen la condición common_field > 'xyz'?20250

Consulta cuatro:

select * from s1 inner join s2 where s1.key2 > 10 and s1.key2 < 1000 and s1.common_field > 'xyz'

Sin embargo, debido a que esta consulta puede usar idx_key2índices, solo es necesario adivinar cuántos registros cumplen las condiciones de los registros que cumplen el rango del índice secundario common_field > 'xyz', es decir, solo cuántos de 989los registros cumplen common_field > 'xyz'las condiciones

Consulta cinco:

select * from s1 inner join s2 where s1.key2 > 10 and s1.key2 < 1000 and s1.key1 in('aa','bb','cc') and s1.common_field > 'xyz'

Esta consulta es similar a la consulta 2, pero después de que la tabla de unidades s1seleccione idx_key1el índice para ejecutar la consulta, el optimizador debe seleccionar cuántos registros de los registros que cumplen con el rango del índice secundario cumplen las siguientes dos condiciones:

  • clave2 > 10 y clave2 < 1000
  • campo_común > 'xyz'

Es decir, el optimizador necesita adivinar 230cuántos de los registros cumplen las dos condiciones anteriores.

Habiendo dicho tanto, en realidad quiero expresar que en estos dos casos, es necesario confiar en las conjeturas al calcular el valor de despliegue de la tabla de control:

  • Si usa una consulta de una sola tabla ejecutada mediante un escaneo completo de la tabla, debe adivinar cuántos registros satisfacen la condición de búsqueda al calcular la distribución de la tabla de control.
  • Si está utilizando un escaneo de una sola tabla realizado por un índice, debe adivinar cuántos registros satisfacen otras condiciones de búsqueda además de las que usan el índice correspondiente al calcular el despliegue de la tabla de control.

MySQL llama a este proceso de adivinanza condition filtering. Por supuesto, este proceso puede usar índices o datos estadísticos, o puede ser pura conjetura de MySQL.Todo el proceso de evaluación es bastante complicado, por lo que lo omitimos.

2.3 Análisis de costos de conexión multimesa

Aquí primero consideramos cuántas secuencias de conexión se pueden generar durante la conexión de varias tablas:

  • Para la conexión de dos mesas, como la conexión entre la mesa A y la mesa B, solo hay dos secuencias de conexión de AB y BA. De hecho, es equivalente a 2 × 1 = 2 secuencias de conexión
  • Para la conexión de tres tablas, como la tabla A, la tabla B y la tabla C, existen seis secuencias de conexión, como ABC, ACB, BAC, BCA, CAB y CBA. De hecho, es equivalente a 3 × 2 × 1 = 6 secuencias de conexión
  • Para cuatro conexiones de mesa, habrá 4 × 3 × 2 × 1 = 24 secuencias de conexión
  • Para la conexión de n tablas, hay una secuencia de conexión n × (n-1) × (n-2) × ··· × 1, que es la secuencia de conexión factorial de n, es decir, n!

4. Constante de costo de ajuste

Presentamos dos antes 成本常数:

  • El costo predeterminado de leer una página es:0.25
  • El costo predeterminado de verificar si un registro coincide con los criterios de búsqueda es:0.1

De hecho, además de estas dos constantes de costo, MySQL también admite muchas, y se almacenan en mysqldos tablas de la base de datos (esta es una base de datos del sistema, que presentamos antes):

mysql> show tables from mysql like '%cost%';
+--------------------------+
| Tables_in_mysql (%cost%) |
+--------------------------+
| engine_cost              |
| server_cost              |
+--------------------------+
2 rows in set (0.06 sec)

Como dijimos antes, la ejecución de una declaración en realidad se divide en dos capas:

  • capa de servidor
  • capa de motor de almacenamiento

Enserver层 la gestión de conexiones, caché de consultas, análisis de sintaxis, optimización de consultas y otras operaciones, las operaciones específicas de acceso a datos se realizan en la capa del motor de almacenamiento. Es decir, el costo de ejecutar una sentencia en la capa del servidor no tiene nada que ver con el motor de almacenamiento que usa la tabla en la que opera, por lo que las constantes de costo correspondientes a estas operaciones se almacenan en la tabla server_cost y dependen de algunos operaciones del motor de almacenamiento La constante de costo correspondiente se almacena en la tabla engine_cost

4.1 tabla server_cost

Las constantes de costo correspondientes a algunas operaciones realizadas en la capa del servidor en la tabla server_cost son las siguientes:

mysql> select * from mysql.server_cost;
+------------------------------+------------+---------------------+---------+---------------+
| cost_name                    | cost_value | last_update         | comment | default_value |
+------------------------------+------------+---------------------+---------+---------------+
| disk_temptable_create_cost   |       NULL | 2023-04-24 19:39:12 | NULL    |            20 |
| disk_temptable_row_cost      |       NULL | 2023-04-24 19:39:12 | NULL    |           0.5 |
| key_compare_cost             |       NULL | 2023-04-24 19:39:12 | NULL    |          0.05 |
| memory_temptable_create_cost |       NULL | 2023-04-24 19:39:12 | NULL    |             1 |
| memory_temptable_row_cost    |       NULL | 2023-04-24 19:39:12 | NULL    |           0.1 |
| row_evaluate_cost            |       NULL | 2023-04-24 19:39:12 | NULL    |           0.1 |
+------------------------------+------------+---------------------+---------+---------------+
6 rows in set (0.00 sec)

Primero veamos qué significa cada columna de server_cost:

  • cost_name: Indica el nombre de la constante de costo
  • cost_value: Indica el valor correspondiente a la constante de coste. Si el valor de esta columna es NULL, significa que la constante de costo correspondiente adoptará el valor por defecto
  • last_update: Indica la hora en que se actualizó el registro por última vez
  • comment: comentario
  • default_value:Por defecto

Se puede ver en el contenido de server_cost que las constantes de costo correspondientes a algunas operaciones en la capa del servidor son las siguientes:

nombre de la constante de costo valores predeterminados describir
disk_temptable_create_cost 40,0 El costo de crear tablas temporales basadas en disco. Si aumenta este valor, el optimizador creará la menor cantidad posible de tablas temporales basadas en disco.
disk_temptable_row_cost 1.0 El costo de escribir o leer un registro en una tabla temporal basada en disco. Si aumenta este valor, el optimizador creará la menor cantidad posible de tablas temporales basadas en disco.
clave_comparar_costo 0.1 El costo de comparar dos registros se usa principalmente en las operaciones de ordenación. Si se aumenta este valor, el costo de la ordenación de archivos aumentará y el optimizador puede estar más inclinado a usar índices para completar la ordenación en lugar de la ordenación de archivos.
memory_temptable_create_cost 2.0 El costo de crear tablas temporales basadas en memoria. Si aumenta este valor, el optimizador creará la menor cantidad posible de tablas temporales basadas en memoria.
memory_temptable_row_cost 0.2 El costo de escribir o leer un registro en una tabla temporal basada en memoria. Si aumenta este valor, el optimizador creará la menor cantidad posible de tablas temporales basadas en memoria.
fila_evaluar_costo 0.2 Este es el costo de detectar si un registro cumple con los criterios de búsqueda que hemos estado usando anteriormente. Aumentar este valor puede hacer que el optimizador se incline más a usar índices en lugar de exploraciones directas de tablas completas.

小提示:
MySQL puede crear una tabla temporal internamente al ejecutar consultas como consultas DISTINCT, consultas de agrupación, consultas de unión y consultas de clasificación bajo ciertas condiciones especiales, y usar esta tabla temporal para ayudar a completar la consulta (por ejemplo, para consultas DISTINCT, puede cree uno con La tabla temporal del índice ÚNICO inserta directamente los registros que deben desduplicarse en esta tabla temporal, y el registro después de completar la inserción es el conjunto de resultados). En el caso de una gran cantidad de datos, es posible crear una tabla temporal basada en disco, es decir, usar motores de almacenamiento como MyISAM e InnoDB para la tabla temporal, y crear una tabla temporal basada en memoria cuando el cantidad de datos no es grande, es decir, para utilizar el almacenamiento de memoria del motor del producto. Todos aquí saben que el costo de crear una tabla temporal y escribir y leer esta tabla temporal sigue siendo muy alto.

server_costLos valores iniciales de estas constantes de costo NULLson , lo que significa que el optimizador usará sus valores por defecto para calcular el costo de una operación, si queremos modificar el valor de una cierta constante de costo, necesitamos hacer dos pasos :

Paso 1: Actualizar la constante de costo que nos interesa

Por ejemplo, si queremos aumentar el costo de verificar si un registro cumple con los criterios de búsqueda 0.3, podemos escribir una declaración de actualización como esta:

update mysql.server_cost set cost_value = 0.4 where cost_name = 'row_evaluate_cost';

Paso 2: Deje que el sistema vuelva a cargar el valor de esta tabla, solo use la siguiente declaración

flush optimizer_costs;

Por supuesto, si desea volver a cambiarlos después de modificar una determinada constante de costo 默认值, puede cost_valueestablecer directamente el valor en NULLy luego usar flush optimizer_costsla declaración para permitir que el sistema lo vuelva a cargar.

4.2 tabla engine_cost

engine_costLas constantes de costo correspondientes a algunas operaciones realizadas en la capa del motor de almacenamiento en la tabla son las siguientes:

mysql> select * from mysql.engine_cost;
+-------------+-------------+------------------------+------------+---------------------+---------+---------------+
| engine_name | device_type | cost_name              | cost_value | last_update         | comment | default_value |
+-------------+-------------+------------------------+------------+---------------------+---------+---------------+
| default     |           0 | io_block_read_cost     |       NULL | 2023-04-24 19:39:12 | NULL    |             1 |
| default     |           0 | memory_block_read_cost |       NULL | 2023-04-24 19:39:12 | NULL    |          0.25 |
+-------------+-------------+------------------------+------------+---------------------+---------+---------------+
2 rows in set (0.01 sec)

En comparación con server_cost, engine_costhay dos columnas más:

  • engine_nameColumna: se refiere al nombre del motor de almacenamiento al que se aplica la constante de costo. Si el valor es predeterminado, significa que la constante de costo correspondiente se aplica a todos los motores de almacenamiento
  • device_typeColumna: se refiere al tipo de dispositivo utilizado por el motor de almacenamiento. Esto es principalmente para distinguir entre discos duros mecánicos convencionales y discos duros de estado sólido. Sin embargo, en MySQL 5.7.21,
    el el valor es 0 por defecto

Podemos ver a partir del contenido de la tabla engine_cost que actualmente solo se admiten dos constantes de costo del motor de almacenamiento:

nombre de la constante de costo valores predeterminados describir
io_block_read_cost 1.0 El costo de leer un bloque del disco. Tenga en cuenta que uso el bloque de palabras, no la página. Para el motor de almacenamiento InnoDB, una página es un bloque, pero para el motor de almacenamiento MyISAM, el valor predeterminado es 4096 bytes como bloque. Aumentar este valor aumentará el costo de E/S, lo que puede hacer que el optimizador se incline más a elegir usar el índice para realizar consultas en lugar de realizar exploraciones de tablas completas.
coste_lectura_bloque_memoria 0.25 Similar al parámetro anterior, excepto que mide el costo correspondiente a leer un bloque de memoria

Después de leer los valores predeterminados de estas dos constantes de costo, ¿estás un poco confundido? ¿Por qué el costo predeterminado de leer un bloque de la memoria es diferente al del disco? Esto se debe principalmente a que, a medida que MySQL evoluciona, MySQL puede predecir con precisión qué bloques están en el disco y cuáles en la memoria.

Al igual que actualizar server_costlos registros en la tabla, también podemos engine_costcambiar la constante de costo sobre el motor de almacenamiento actualizando los registros en la tabla, y también podemos engine_costagregar una constante de costo específica para un determinado motor de almacenamiento insertando un nuevo registro para la tabla:

Paso 1: Inserte una constante de costo para un determinado motor de almacenamiento.
Por ejemplo, si queremos aumentar el costo de E/S de la página del motor de almacenamiento InnoDB, simplemente escriba una declaración de inserción normal:

insert into mysql.engine_cost values ('innodb', 0, 'io_block_read_cost', 2.0, current_timestamp, 'increase innodb i/o cost');

Paso 2: Deje que el sistema vuelva a cargar el valor de esta tabla usando la siguiente declaración:

flush optimizer_costs;

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/130779501
Recomendado
Clasificación