¿Es posible utilizar join en la optimización avanzada de MySQL?

En la producción real, las preguntas sobre el uso de declaraciones de combinación generalmente se concentran en las siguientes dos categorías:

  • Nuestro DBA no permite el uso de join, ¿cuál es el problema con el uso de join?
  • Si hay dos mesas con diferentes tamaños para unir, ¿qué mesa debe usarse como mesa de manejo?

En el artículo de hoy, primero le diré cómo se ejecuta la declaración de combinación y luego responderé estas dos preguntas.

Para facilitar el análisis cuantitativo, sigo creando dos tablas t1 y t2 para explicarles.


CREATE TABLE `t2` (
  `id` int(11) NOT NULL,
  `a` int(11) DEFAULT NULL,
  `b` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `a` (`a`)
) ENGINE=InnoDB;

drop procedure idata;
delimiter ;;
create procedure idata()
begin
  declare i int;
  set i=1;
  while(i<=1000)do
    insert into t2 values(i, i, i);
    set i=i+1;
  end while;
end;;
delimiter ;
call idata();

create table t1 like t2;
insert into t1 (select * from t2 where id<=100)

Como puede ver, estas dos tablas tienen un ID de índice de clave principal y un índice a, y no hay índice en el campo b. El procedimiento almacenado idata () inserta 1000 filas de datos en la tabla t2 y 100 filas de datos en la tabla t1.

Unión de bucle anidado de índice

Echemos un vistazo a esta declaración:


select * from t1 straight_join t2 on (t1.a=t2.a);

Si usa la declaración de unión directamente, el optimizador de MySQL puede elegir la tabla t1 o t2 como tabla de manejo, lo que afectará el proceso de ejecución de nuestro análisis de la declaración SQL.

Por lo tanto, para facilitar el análisis de los problemas de rendimiento durante la ejecución, cambié a straight_join para permitir que MySQL use un método de conexión fijo para ejecutar la consulta, de modo que el optimizador solo se unirá de la forma que especifiquemos.

 

En esta declaración, t1 es la tabla impulsada y t2 es la tabla impulsada. Ahora, echemos un vistazo al resultado de explicación de esta declaración.

Se puede ver que en esta declaración, hay un índice en el campo a de la tabla conducida t2, y este índice se usa en el proceso de unión, por lo que el flujo de ejecución de esta declaración es el siguiente:

  1. Leer una fila de datos R de la tabla t1;
  2. De la fila de datos R, saque el campo a y búsquelo en la tabla t2 ;
  3. Saque las filas que cumplen las condiciones en la tabla t2 y forme una fila con R como parte del conjunto de resultados;
  4. Repita los pasos 1 a 3 hasta que finalice el ciclo de la tabla t1.

Este proceso consiste en recorrer primero la tabla t1, y luego de acuerdo con el valor de a en cada fila de datos tomados de la tabla t1, ir a la tabla t2 para encontrar los registros que cumplen las condiciones. En forma, este proceso es similar a la consulta anidada cuando escribimos el programa, y ​​se puede usar el índice de la tabla conducida, por lo que lo llamamos "Index Nested-Loop Join", o NLJ para abreviar .

 

Su diagrama de flujo correspondiente es el siguiente:

En este proceso:

  1. Realice un escaneo completo de la tabla en la tabla de unidades t1, este proceso necesita escanear 100 filas;
  2. Para cada fila de R, vaya a la tabla t2 de acuerdo con el campo a y siga el proceso de búsqueda de árbol. Dado que los datos que construimos están en una correspondencia uno a uno, solo se escanea una línea en cada proceso de búsqueda y se escanea un total de 100 líneas;
  3. Por lo tanto, en todo el proceso de ejecución, el número total de líneas de exploración es 200.

 

Ahora que conocemos el proceso, intente responder las dos preguntas al comienzo del artículo.

Veamos primero la primera pregunta: ¿podemos usar join?

Suponiendo que no se usa la combinación, solo podemos usar una consulta de tabla única. Veamos los requisitos de la declaración anterior y cómo lograrlo con una sola consulta de tabla.

  1. Ejecute select * from t1 para encontrar todos los datos en la tabla t1, hay 100 filas;
  2. Recorra estas 100 filas de datos:
  • Tome el valor del campo a de cada fila R $ Ra
  • ; 执行 seleccione * de t2 donde a = $ Ra ;
  • El resultado devuelto y R forman una fila del conjunto de resultados.

 

Se puede ver que en este proceso de consulta, se escanearon 200 filas, pero se ejecutaron un total de 101 sentencias, que son 100 interacciones más que la unión directa. Además, el cliente tiene que empalmar sentencias SQL y resultados por sí mismo. Obviamente, esto no es tan bueno como una combinación directa.

Echemos un vistazo a la segunda pregunta: ¿cómo elegir una mesa de drive?

Durante la ejecución de esta declaración de combinación, la tabla inicial es una exploración de tabla completa y la tabla conducida es una búsqueda de árbol .

  • Suponga que el número de filas en la tabla conducida es M. Cada vez que verifica una fila de datos en la tabla conducida, primero debe buscar en el índice a y luego buscar en el índice de clave principal.
  • La complejidad aproximada de buscar un árbol cada vez es el logaritmo de M con la base de 2, registrado como log2M, por lo que la complejidad de tiempo de buscar una fila en la tabla conducida es 2 * log2M.
  • Suponiendo que el número de filas en la tabla conductora es N, el proceso de ejecución debe escanear las N filas de la tabla conductora, y luego, para cada fila, coincide una vez en la tabla conducida.
  • Por lo tanto, la complejidad aproximada de todo el proceso de ejecución es N + N * 2 * log2M.

Obviamente, N tiene un mayor impacto en el número de filas de escaneo, por lo que la tabla pequeña debe usarse como tabla de manejo.

(Si no cree que este efecto sea tan "obvio", puede entenderlo así: si N se amplía 1000 veces, el número de líneas de exploración se ampliará 1000 veces; mientras que M se ampliará 1000 veces, el número de líneas de exploración se ampliará menos de 10 veces).

 

Para resumir aquí, hemos llegado a dos conclusiones a través del análisis anterior:

  1. Al utilizar la instrucción de unión, el rendimiento es mejor que el rendimiento de dividir a la fuerza en varias tablas individuales para ejecutar instrucciones SQL;
  2. Si usa la declaración de unión, debe dejar que la mesa pequeña sea la mesa de conducción. Sin embargo, debe tener en cuenta que la premisa de esta conclusión es "se puede utilizar el índice de la tabla conducida" .

 

 

A continuación, veamos la situación en la que la tabla conducida no utiliza el índice.

Unión simple de bucle anidado

Ahora, cambiamos la declaración SQL a esto:


select * from t1 straight_join t2 on (t1.a=t2.b);

 

Dado que no hay índice en el campo b de la tabla t2, cuando se usa de nuevo el flujo de ejecución de la Figura 2, se requiere un escaneo completo de la tabla cada vez que t2 coincide.

 

Primero puede imaginar este problema y continuar usando el algoritmo de la Figura 2. ¿Puede obtener el resultado correcto?

Si solo observa los resultados, este algoritmo es correcto, y este algoritmo también tiene un nombre llamado " Unión de bucle anidado simple ".

Sin embargo, de esta manera, esta solicitud SQL escaneará la tabla t2 hasta 100 veces, y se escaneará un total de 100 * 1000 = 100,000 filas. Estas son solo dos tablas pequeñas. Si t1 y t2 son tablas con 100.000 filas (por supuesto, esto todavía está dentro del alcance de la tabla pequeña), se escanearán 10 mil millones de filas. Este algoritmo parece demasiado "engorroso".

Por supuesto, MySQL no usa este algoritmo Simple Nested-Loop Join, pero usa otro algoritmo llamado "Block Nested-Loop Join ", conocido como BNL.

 

Bloquear unión de bucle anidado

En este momento, no hay un índice disponible en la tabla conducida y el flujo del algoritmo es así:

  1. Lea los datos de la tabla t1 en la memoria de subprocesos join_buffer Dado que escribimos select * en esta declaración, colocamos toda la tabla t1 en la memoria;
  2. Escanee la tabla t2, saque cada fila de la tabla t2, compárela con los datos en join_buffer y devuélvala como parte del conjunto de resultados si se cumple la condición de unión.

El diagrama de flujo de este proceso es el siguiente:

En consecuencia, el resultado de explicación de esta declaración SQL es el siguiente:

Se puede ver que en este proceso, se realiza un escaneo de tabla completo en las tablas t1 y t2, por lo que el número total de filas escaneadas es 1100.

Dado que join_buffer está organizado en una matriz desordenada, se requieren 100 juicios para cada fila en la tabla t2, y el número total de juicios que deben realizarse en la memoria es: 100 * 1000 = 100,000 veces.

Como dijimos anteriormente, si usa el algoritmo Simple Nested-Loop Join para realizar consultas, el número de filas escaneadas también es de 100,000 filas. Por lo tanto, en términos de complejidad temporal, estos dos algoritmos son iguales.

Sin embargo, los 100.000 juicios del algoritmo Block Nested-Loop Join son operaciones de memoria, que son mucho más rápidas y tienen un mejor rendimiento. A continuación, echemos un vistazo, en este caso, qué tabla debe seleccionarse como tabla de manejo.

 

Suponiendo que el número de filas en la tabla pequeña es N y el número de filas en la tabla grande es M, entonces en este algoritmo:

  1. Ambas tablas realizan un escaneo de tabla completo, por lo que el número total de filas escaneadas es M + N;
  2. El número de juicios en la memoria es M * N.

Se puede observar que no hay diferencia entre M y N en estos dos cálculos, por lo tanto, en este momento, ya sea para elegir una mesa grande o una mesa pequeña como mesa de conducción, el tiempo de ejecución es el mismo.

 

Entonces, puede preguntar de inmediato. En este ejemplo, la tabla t1 tiene solo 100 filas. ¿Qué pasa si la tabla t1 es una tabla grande y join_buffer no cabe?

El tamaño de join_buffer lo establece el parámetro join_buffer_size, y el valor predeterminado es 256k. Si no puede poner todos los datos en la tabla t1, la estrategia es muy simple, que consiste en ponerlos en secciones. Cambié join_buffer_size a 1200 , y luego ejecuté :


select * from t1 straight_join t2 on (t1.a=t2.b);

El proceso de ejecución se convierte en:

  1. Escanee la tabla t1, lea las filas de datos secuencialmente y colóquelas en join_buffer, después de poner el join_buffer en la fila 88, continúe con el paso 2;
  2. Escanee la tabla t2, saque cada fila en t2, compare con los datos en join_buffer y regrese como parte del conjunto de resultados si se cumple la condición de unión;
  3. Borrar join_buffer;
  4. Continúe escaneando la tabla t1, lea secuencialmente las últimas 12 filas de datos en join_buffer y continúe con el paso 2.

El diagrama de flujo de ejecución se vuelve así:

Los pasos 4 y 5 de la figura indican que join_buffer se borra y se reutiliza.

 

Este proceso refleja el origen del "Bloque" en el nombre del algoritmo , que significa "unirse por bloque". Puede verse que en este momento, debido a que la tabla t1 se divide en dos y se coloca en join_buffer, la tabla t2 se escaneará dos veces. Aunque join_buffer se divide en dos veces, el número de veces para juzgar la condición de equivalencia sigue siendo el mismo, aún (88 + 12) * 1000 = 100.000 veces .

 

Veamos la selección de la tabla de controladores en este caso.

  1. Suponiendo que el número de filas de datos en la tabla impulsada es N, debe dividirse en K segmentos para completar el flujo del algoritmo, y el número de filas de datos en la tabla impulsada es M.
  2. Tenga en cuenta que K aquí no es una constante. Cuanto mayor sea N, mayor será K. Por lo tanto, denote K como λ * N. Obviamente, el rango de valores de λ es (0,1).
  3. Por lo tanto, en la ejecución de este algoritmo: el número de líneas de exploración es N + λ * N * M; la memoria se juzga N * M veces.
  4. Obviamente, el número de juicios de memoria no se ve afectado por qué mesa se selecciona como mesa de conducción. Teniendo en cuenta el número de líneas de exploración, cuando se determina el tamaño de M y N, N es menor y el resultado de todo el cálculo será menor.

Por tanto, la conclusión es que la mesa pequeña debería utilizarse como mesa de dirección.

Por supuesto, encontrará que en la fórmula N + λ * N * M, λ es el factor clave que afecta el número de líneas de exploración. Cuanto menor sea el valor, mejor.

Solo dijimos que cuanto mayor es N, mayor es el número de segmentos K. Entonces, cuando N es fijo, ¿qué parámetros afectarán el tamaño de K?

  • (Es decir, el tamaño de λ) La respuesta es join_buffer_size. Cuanto mayor sea el join_buffer_size, más filas se pueden colocar a la vez, menor será el número de segmentos y menos escaneos de tabla completa de la tabla conducida.
  • Por eso, es posible que vea algunas sugerencias que le dicen que si su declaración de unión es lenta, aumente el tamaño de join_buffer_size.

Después de comprender los dos algoritmos que implementa MySQL, intentemos responder las dos preguntas al comienzo del artículo.

 

  • La primera pregunta: ¿Puedo usar la declaración de unión?
  1. Si se puede usar el algoritmo Index Nested-Loop Join, lo que significa que se puede usar el índice de la tabla conducida, en realidad no hay problema;
  2. Si se utiliza el algoritmo Block Nested-Loop Join, el número de líneas de exploración será demasiado. Especialmente la operación de unión en una tabla grande, esto puede tener que escanear la tabla impulsada muchas veces, lo que consumirá muchos recursos del sistema. Intente no utilizar este tipo de combinación.

Entonces, cuando decida si usar la declaración de combinación, simplemente mire el resultado de la explicación y vea si la palabra "Bloquear bucle anidado" aparece en el campo Extra.

 

  • La segunda pregunta es: si desea utilizar join, ¿debería elegir una mesa grande como mesa de conducción o una mesa pequeña como mesa de conducción?
  1. Si se trata del algoritmo Index Nested-Loop Join, se debe seleccionar una pequeña tabla como tabla inicial;
  2. Si es el algoritmo Block Nested-Loop Join: es lo mismo cuando join_buffer_size es lo suficientemente grande;
  3. Cuando join_buffer_size no es lo suficientemente grande (esta situación es más común), se debe seleccionar una tabla pequeña como tabla de manejo.
  4.  

Por tanto, la conclusión de esta pregunta es: utilice siempre mesas pequeñas como mesas de dirección.

 

Por supuesto, aquí necesito explicar lo que se llama un "reloj pequeño".

 

Nuestro ejemplo anterior es incondicional. Si agrego t2.id <= 50 a la condición where de la declaración, veamos estas dos declaraciones:


select * from t1 straight_join t2 on (t1.b=t2.b) where t2.id<=50;
select * from t2 straight_join t1 on (t1.b=t2.b) where t2.id<=50;

Tenga en cuenta que para evitar que la tabla conducida de las dos declaraciones use un índice, el campo de combinación usa el campo b sin índice. Pero si se usa la segunda declaración, join_buffer solo necesita colocarse en las primeras 50 líneas de t2, lo que obviamente es mejor.

Entonces, aquí, "las primeras 50 filas de t2" es la tabla relativamente pequeña, es decir, la "tabla pequeña". Veamos otro conjunto de ejemplos:

 


select t1.b,t2.* from  t1  straight_join t2 on (t1.b=t2.b) where t2.id<=100;
select t1.b,t2.* from  t2  straight_join t1 on (t1.b=t2.b) where t2.id<=100;

En este ejemplo, las tablas t1 y t2 tienen solo 100 filas que participan en la combinación. Sin embargo, los datos colocados en join_buffer en estas dos declaraciones son diferentes cada vez:

  • La tabla t1 solo verifica el campo b, por lo que si t1 se coloca en join_buffer, solo el valor de b debe colocarse en join_buffer;
  • La tabla t2 necesita verificar todos los campos, por lo que si coloca la tabla t2 en join_buffer, debe ingresar tres campos id, ay b.

Aquí, deberíamos elegir la tabla t1 como tabla de conducción. Es decir, en este ejemplo, "sólo una columna de la tabla t1 que participa en la combinación" es la tabla relativamente pequeña.

Por lo tanto, para ser más precisos, al decidir qué tabla usar como tabla de inicio, las dos tablas deben filtrarse de acuerdo con sus respectivas condiciones. Una vez completado el filtrado, se calcula el volumen total de datos de cada campo que participa en la combinación, y se debe calcular la tabla con el volumen de datos más pequeño. Es la "tabla pequeña", que debe usarse como tabla de manejo.

 

resumen

Hoy, presenté dos posibles algoritmos para que MySQL ejecute sentencias de combinación.Estos dos algoritmos están determinados por si se puede usar el índice de la tabla conducida.

El hecho de que se pueda utilizar el índice de la tabla conducida tiene un gran impacto en el rendimiento de la declaración de combinación.

A través del análisis del proceso de ejecución de los dos algoritmos de Index Nested-Loop Join y Block Nested-Loop Join, también obtuvimos las respuestas a las dos preguntas al comienzo del artículo:

  • Si puede usar el índice de la tabla conducida, la declaración de combinación todavía tiene sus ventajas; no puede usar el índice de la tabla conducida, solo puede usar el algoritmo Block Nested-Loop Join e intente no usar tales declaraciones;
  • Cuando se usa join, las tablas pequeñas deben usarse como tablas de manejo.

 

Artículo extraído de: "MySQL45 Lectures"

Este artículo es uno de los pocos artículos completos que he visto sobre los principios de jion. Ha sido genial para mi. Por la presente registre.

Supongo que te gusta

Origin blog.csdn.net/m0_46405589/article/details/115329814
Recomendado
Clasificación