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?
Tabla de contenido
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.25
Y 0.1
estos 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:
id
El índice agrupado creado para la columna;key1
Un índice secundario creado para la columna;key2
Un índice secundario único creado para la columna;key3
Un índice secundario creado para la columna;- Un índice secundario compuesto (conjunto) creado para las columnas
key_part1
,key_part2
, .key_part3
Luego, debemos insertar 20000
un 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 LIKE
operadores, 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_part1
uselike
operadores para comparar cadenas que comiencen con comodines, y no se pueden usar índicescommon_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 keys
sum idx_key1
e 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 status
después de la declaración, por ejemplo, si queremos ver la información estadística de esta tabla , podemos escribirlo así:like
demo8
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. ParaMyISAM
las tablas que usan motores de almacenamiento, este valor es exacto y paraInnoDB
las tablas que usan motores de almacenamiento, este valor es una estimación. Como se puede ver en los resultados de la consulta, dado que nuestrademo8
tabla usaInnoDB
un motor de almacenamiento, aunque en realidad hay 20 000 registros en la tabla, el valorshow table status
mostradoRows
es 20 187 registros. -
Data_length
: Esta opción indica el número de bytes de espacio de almacenamiento ocupado. Para una tabla que usaMyISAM
un motor de almacenamiento, este valor es el tamaño del archivo de datos Para unaInnoDB
tabla que usa un motor de almacenamiento, este valor es equivalente al tamaño del espacio de almacenamiento ocupado por el índice agrupadoEs decir, el tamaño del valor se puede calcular así:
Data_length = 聚簇索引的页面数量 * 每个页面的大小
Nuestra
demo8
tabla usa el16KB
tamañ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/O
Costo: 97 * 0.25 =24.25
97
se refiere a la cantidad de páginas ocupadas por el índice agrupado,0.25
se refiere a la constante de costo de cargar una páginaCPU
Costo: 20187 * 0.1 =2018.7
20187
se refiere a la cantidad de registros en la tabla en los datos estadísticos, queInnoDB
es un valor estimado para el motor de almacenamiento,0.1
y se refiere a la constante de costo requerida para acceder a un registro总成本:
24,25 +2018,7 =2042.95
En resumen, demo8
el costo total requerido para el escaneo completo de la tabla es 2042.95
cargar directamente el código, sin tonterías, y una verificación magnífica.
mysql> explain format=json select * from demo8 ;
小提示:
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_key1
estos idx_key2
dos í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_key2
uso idx_key1
. .
Coste de las consultas realizadas con idx_key2
idx_key2
La condición de búsqueda correspondiente es: key2 > 10 and key2 < 1000
, es decir, el intervalo de rango correspondiente es: ( 10
, 1000
), y idx_key2
el diagrama de búsqueda es el siguiente:
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 rangoI/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 calcular10,1000
cuántos registros del índice secundario contiene idx_key2 en el rango ( ). El proceso de cálculo es el siguiente:-
Paso 1: Primero
key2 > 10
visiteidx_key2
el índice de árbol B+ correspondiente de acuerdo con esta condición, y busquekey2 > 10
el 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 < 1000
delidx_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 < 1000
condiciones 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+: -
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_key2
en el intervalo ( ). El costo de la CPU para leer este registro de índice secundario es:10, 1000
989
989
989 * 0.1 = 98.9
¿Dónde 989
está el número de registros de índice secundario que deben leerse 0.1
y 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
989
es el número esperado de registros de índice secundario y0.25
es 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 < 1000
distintas 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 agrupado989
, 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.1
que 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';
小提示
:
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_key1
La 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_key1
de la búsqueda es el siguiente:
Similar al idx_key2
caso de uso, también necesitamos idx_key1
la 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 queI/O
el 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 = 230
y el costo de leer estos registros de índice secundario CPU
es: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/O
costo requerido es:230 x 0.25 = 57.5
- Después de regresar a la tabla, se obtiene el registro completo del usuario, y
CPU
el 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';
¿Es posible usar Index Merge?
En este ejemplo, las condiciones de búsqueda key1
para 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.key2
AND
idx_key1
idk_key2
Intersection
小提示:
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_key2
Costos utilizados :445.31
idx_key1
Costos utilizados :104.26
Obviamente, idx_key1
el costo más bajo para usar, por lo que, por supuesto, elija idx_key1
ejecutar 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 |
Packed
Excepto 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, Cardinality
que 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 index
atributo 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 dive
para 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 status
que muestra la declaraciónRows
, es decir, cuántos registros hay en una tabla -
Usando las propiedades
show index
mostradas 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 demo8
de 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_key1
20187
Cardinality
256
20187÷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 79
un 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 dive
es 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 s1
la 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 20250
lo considerará directamente como el valor de distribución en s1
la 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 989
los 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 s1
seleccione idx_key1
el í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 230
cuá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 mysql
dos 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 costocost_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 defectolast_update
: Indica la hora en que se actualizó el registro por última vezcomment
: comentariodefault_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_cost
Los valores iniciales de estas constantes de costo NULL
son , 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_value
establecer directamente el valor en NULL
y luego usar flush optimizer_costs
la declaración para permitir que el sistema lo vuelva a cargar.
4.2 tabla engine_cost
engine_cost
Las 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_cost
hay dos columnas más:
engine_name
Columna: 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 almacenamientodevice_type
Columna: 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_cost
los registros en la tabla, también podemos engine_cost
cambiar la constante de costo sobre el motor de almacenamiento actualizando los registros en la tabla, y también podemos engine_cost
agregar 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