Postura correcta para la clasificación aleatoria de MySQL

Hay una estructura de tabla:
CREATE TABLE `words` (
  ` id` int (11) NOT NULL AUTO_INCREMENT,
  `word` varchar (64) DEFAULT NULL,
  PRIMARY KEY (` id`)
) ENGINE = InnoDB;

Hay 10,000 filas insertadas en la tabla y 3 palabras se seleccionan al azar de ellas.

La forma más fácil

mysql> seleccionar palabra del orden de palabras por rand () límite 3;

Aunque esta declaración SQL es muy simple, el proceso de ejecución es un poco complicado.

El campo Extra muestra Usando temporal y Usando ordenamiento de archivos, lo que indica que se necesita una tabla temporal y que se requiere ordenar en la tabla temporal. Para las tablas InnoDB, realizar una clasificación de campo completo reducirá el acceso al disco, por lo que será preferible.

Sin embargo, para la tabla de memoria, el proceso de regresar a la tabla simplemente accede a la memoria para obtener los datos en función de la ubicación de la fila de datos, lo que no dará lugar a un acceso múltiple al disco. MySQL elegirá la clasificación por filas en este momento .

El flujo de ejecución de esta declaración es así:

  1. Crea una tabla temporal. Se utiliza el motor de memoria. Hay dos campos en la tabla. El primer campo es de tipo double y está marcado como campo R, y el segundo campo es de tipo varchar (64) y está marcado como campo W. Además, esta tabla no está indexada.
  2. De la tabla de palabras, saque todos los valores de las palabras en el orden de la clave principal. Para cada valor de palabra, llame a la función rand () para generar un decimal aleatorio mayor que 0 y menor que 1, y almacene este decimal aleatorio y palabra en los campos R y W de la tabla temporal respectivamente. Hasta ahora, el número de escaneados filas es 10000 .
  3. Ahora, la tabla temporal tiene 10.000 filas de datos. A continuación, debe ordenar por campo R en esta tabla temporal de memoria no indexada.
  4. Inicializa sort_buffer. Hay dos campos en sort_buffer, uno es de tipo doble y el otro es de tipo entero.
  5. Obtenga el valor R y la información de ubicación línea por línea de la tabla temporal de memoria y guárdelos en dos campos en sort_buffer. Este proceso requiere un escaneo completo de la tabla. En este momento, el número de filas escaneadas aumenta en 10,000 y se convierte en 20,000.
  6. Ordene según el valor de R en sort_buffer. Tenga en cuenta que este proceso no implica operaciones de tabla, por lo que no aumentará el número de filas escaneadas.
  7. Una vez completada la clasificación, se recupera la información de ubicación de los primeros tres resultados, el valor de la palabra se recupera de la tabla de memoria temporal a su vez y se devuelve al cliente. En este proceso, se accede a las tres filas de datos de la tabla y el número total de filas escaneadas se convierte en 20003 .

Nota: ¿Cuál es el concepto de "información de ubicación" en el paso 5? El motor de MEMORIA no es una tabla organizada por índices. En este ejemplo, puede pensarlo como una matriz. Por lo tanto, este rowid es en realidad el subíndice de la matriz.

Utilice el registro lento para verificar:

# Query_time: 0.900376  Lock_time: 0.000347 Rows_sent: 3 Rows_examined: 20003
SET timestamp=1541402277;
select word from words order by rand() limit 3;

order by rand () usa una tabla temporal de memoria, y el método de ordenación rowid se usa al ordenar la tabla temporal de memoria.

tmp_table_size Esta configuración limita el tamaño de la tabla temporal de memoria, el valor predeterminado es 16M. Si el tamaño de la tabla temporal excede tmp_table_size, la tabla temporal en memoria se convertirá en una tabla temporal de disco . El motor predeterminado utilizado para las tablas temporales de disco es InnoDB, que está controlado por el parámetro internal_tmp_disk_storage_engine .

Cuando se utilizan tablas temporales de disco, el ejemplo anterior corresponde al proceso de clasificación de una tabla InnoDB sin un índice explícito.

set tmp_table_size=1024;
set sort_buffer_size=32768;
set max_length_for_sort_data=16;
/* 打开 optimizer_trace,只对本线程有效 */
SET optimizer_trace='enabled=on'; 
/* 执行语句 */
select word from words order by rand() limit 3;
/* 查看 OPTIMIZER_TRACE 输出 */
SELECT * FROM `information_schema`.`OPTIMIZER_TRACE`\G

    

 Sort_mode muestra la clasificación de rowid, y las filas que están involucradas en la clasificación son el campo R de valor aleatorio y el campo rowid.

El valor aleatorio almacenado en el campo R es de solo 8 bytes, el rowid es de 6 bytes y el número total de filas de datos es 10000. Esto es 140000 bytes, lo que excede los 32768 bytes definidos por sort_buffer_size. Sin embargo, el valor de number_of_tmp_files es en realidad 0. Porque la clasificación de esta declaración SQL es un nuevo algoritmo de clasificación introducido por la versión 5.6 de MySQL, a saber: algoritmo de clasificación de cola de prioridad. A partir del resultado de OPTIMIZER_TRACE, también se puede ver la parte elegida = verdadera de filesort_priority_queue_optimization.

De hecho, nuestra declaración SQL actual solo necesita tomar las 3 filas con el valor más pequeño de R. Si se usa el algoritmo de clasificación de fusión, aunque los primeros 3 valores se pueden obtener al final, este algoritmo clasificará las 10,000 filas de datos. Es innecesario.

El algoritmo de la cola de prioridad puede obtener con precisión solo tres valores mínimos. El proceso de ejecución es el siguiente:

  1. Para ordenar los 10,000 (R, rowid), primero tome las primeras tres filas y construya un montón;
  2. Tome la siguiente fila (R ', rowid') y compárela con la R más grande en el montón actual. Si R 'es menor que R, elimine este (R, rowid) del montón y reemplácelo con (R', rowid ');
  3. Repita el paso 2 hasta que se compare el número 10000 (R ', rowid').

La consulta SQL del artículo anterior también tiene un límite de 1000. Si se usa el algoritmo de cola de prioridad, el tamaño del montón que debe mantenerse es de 1000 filas (nombre, identificador de fila), que excede el tamaño de sort_buffer_size que establecí, así que solo puede usar el algoritmo de clasificación de combinación.

En resumen, no importa qué tipo de tabla temporal se utilice, ordenar por rand () hará que el proceso de cálculo sea muy complicado y requiera una gran cantidad de filas de escaneo, por lo que el consumo de recursos del proceso de clasificación será muy grande.

Ordenar correctamente al azar

Primero simplifique el problema, si solo se selecciona al azar un valor de palabra:

  1. Obtenga el valor máximo M y el valor mínimo N del ID de clave principal de esta tabla;
  2. Utilice una función aleatoria para generar un número entre el máximo y el mínimo X = (MN) * rand () + N;
  3. Tome la fila con la primera identificación no menor que X.

Por el momento, se le llama algoritmo aleatorio 1, observe la secuencia de declaraciones de ejecución:

mysql> select max(id),min(id) into @M,@N from t ;
set @X= floor((@M-@N+1)*rand() + @N);
select * from t where id >= @X limit 1;

Este método es muy eficiente, porque tanto max (id) como min (id) no necesitan escanear el índice, y el tercer paso de selección también puede usar el índice para ubicar rápidamente, lo que puede considerarse que solo escanea 3 filas. Pero, de hecho, este algoritmo en sí no cumple estrictamente con los requisitos aleatorios del título, porque puede haber huecos en el ID, por lo que la probabilidad de elegir diferentes filas es diferente , no verdaderamente aleatoria.

Para obtener resultados estrictamente aleatorios, puede utilizar el siguiente proceso:

  1. Obtenga el número de filas en toda la tabla y regístrelo como C.
  2. Obtenga Y = piso (C * rand ()). El papel de la función de piso aquí es tomar la parte entera.
  3. Utilice el límite Y, 1 para obtener una fila.

Este es el algoritmo aleatorio 2, que resuelve el obvio problema de probabilidad desigual en el algoritmo 1. El enfoque de MySQL para procesar el límite Y, 1 es leerlos uno por uno en orden, descartar el primer Y y luego usar el siguiente registro como resultado de retorno , por lo que este paso necesita escanear Y + 1 filas. Además, la línea C escaneada en el primer paso requiere escanear líneas C + Y + 1 en total, y el costo de ejecución es mayor que el costo del algoritmo aleatorio 1.

Si se calcula de acuerdo con esta tabla con 10000 filas, C = 10000, si es aleatorio a un valor de Y mayor, el número de filas escaneadas es casi 20000, que es cercano al número de filas escaneadas de orden por rand (), pero aún más que ordenar por rand () es mucho menos costoso de ejecutar. Debido a que el algoritmo aleatorio 2 realiza el límite para obtener datos de acuerdo con la clasificación de la clave primaria y la clasificación del índice natural de la clave primaria, este proceso se omite aquí.

Si seguimos la idea del algoritmo aleatorio 2, necesitamos seleccionar aleatoriamente 3 valores de palabras:

  1. Obtenga el número de filas en toda la tabla, denotado como C;
  2. Obtenga Y1, Y2, Y3 de acuerdo con el mismo método aleatorio;
  3. Ejecute tres declaraciones de límite Y, 1 para obtener tres filas de datos.

El número total de líneas de exploración de este algoritmo aleatorio es C + (Y1 + 1) + (Y2 + 1) + (Y3 + 1). De hecho, puede seguir optimizándose para reducir aún más el número de líneas de exploración:

  1. Después de la salida aleatoria de Y1, Y2, Y3, calcule Ymax e Ymin;
  2. 再用 seleccione id del límite t Ymin , (Ymax - Ymin + 1) ;
  3. Después de obtener el conjunto de id, calcule los tres id correspondientes a Y1, Y2 e Y3;
  4. 最后 seleccione * de t donde id en (id1, id2, id3)。

El número de líneas escaneadas de esta manera debe ser C + Ymax + 3.

 

 

Fuente del contenido: Lin Xiaobin "45 conferencias sobre combate real de MySQL"

Supongo que te gusta

Origin blog.csdn.net/qq_24436765/article/details/112650812
Recomendado
Clasificación