¿Por qué el desplazamiento de LÍMITE superior MYSQL ralentiza la consulta?

173

En resumen: una tabla con más de 16 millones de registros [2 GB de tamaño]. Cuanto mayor es el desplazamiento de LIMIT con SELECT, más lenta se vuelve la consulta, cuando se usa ORDER BY * primary_key *

Entonces

SELECT * FROM large ORDER BY `id`  LIMIT 0, 30 

toma mucho menos de

SELECT * FROM large ORDER BY `id` LIMIT 10000, 30 

Eso solo ordena 30 registros y lo mismo de todos modos. Entonces no es la sobrecarga de ORDER BY.
Ahora, al buscar las últimas 30 filas, toma alrededor de 180 segundos. ¿Cómo puedo optimizar esa simple consulta?

Rahman
fuente
NOTA: soy el autor. MySQL no se refiere al índice (PRIMARIO) en los casos anteriores. consulte el siguiente enlace del usuario "Quassnoi" para obtener una explicación.
Rahman

Respuestas:

197

Es normal que las compensaciones más altas ralenticen la consulta, ya que la consulta necesita contar los primeros OFFSET + LIMITregistros (y tomar solo LIMITde ellos). Cuanto mayor sea este valor, más tiempo se ejecutará la consulta.

La consulta no puede ir directamente a OFFSET, porque, en primer lugar, los registros pueden tener una longitud diferente y, en segundo lugar, puede haber huecos en los registros eliminados. Necesita verificar y contar cada registro en su camino.

Suponiendo que se idtrata PRIMARY KEYde una MyISAMtabla, puede acelerarla utilizando este truco:

SELECT  t.*
FROM    (
        SELECT  id
        FROM    mytable
        ORDER BY
                id
        LIMIT 10000, 30
        ) q
JOIN    mytable t
ON      t.id = q.id

Ver este artículo:

Quassnoi
fuente
77
El comportamiento de MySQL "búsqueda en la primera fila" fue la respuesta por qué está hablando tanto tiempo. Según el truco que proporcionó, solo los identificadores coincidentes (directamente por el índice) están vinculados, lo que ahorra búsquedas innecesarias de filas de demasiados registros. Eso hizo el truco, ¡hurra!
Rahman
44
@harald: ¿qué quieres decir exactamente con "no trabajar"? Esta es una mejora pura del rendimiento. Si no hay un índice que pueda utilizar ORDER BYo el índice cubre todos los campos que necesita, no necesita esta solución.
Quassnoi
66
@ f055: la respuesta dice "acelerar", no "hacer instantáneo". ¿Has leído la primera oración de la respuesta?
Quassnoi
3
¿Es posible ejecutar algo como esto para InnoDB?
NeverEndingQueue
3
@Lanti: publíquelo como una pregunta separada y no olvide etiquetarlo postgresql. Esta es una respuesta específica de MySQL.
Quassnoi
220

Yo tuve exactamente el mismo problema. Dado el hecho de que desea recopilar una gran cantidad de estos datos y no un conjunto específico de 30, probablemente ejecutará un bucle e incrementará el desplazamiento en 30.

Entonces, lo que puedes hacer es:

  1. Mantenga la última identificación de un conjunto de datos (30) (por ejemplo, lastId = 530)
  2. Agregar la condición WHERE id > lastId limit 0,30

Por lo tanto, siempre puede tener un desplazamiento CERO. Te sorprenderá la mejora del rendimiento.

Nikos Kyr
fuente
¿Funciona esto si hay lagunas? ¿Qué sucede si no tiene una única clave única (una clave compuesta, por ejemplo)?
xaisoft
8
Puede que no sea obvio para todos que esto solo funciona si su conjunto de resultados está ordenado por esa clave, en orden ascendente (para el orden descendente la misma idea funciona, pero cambie> lastid a <lastid). No importa si es el clave principal u otro campo (o grupo de campos)
Eloff
¡Bien hecho ese hombre! Una solución muy simple que ha resuelto mi problema :-)
oodavid
30
Solo una nota de que el límite / desplazamiento a menudo se usa en resultados paginados, y mantener lastId simplemente no es posible porque el usuario pueda saltar a cualquier página, no siempre a la página siguiente. En otras palabras, el desplazamiento a menudo debe calcularse dinámicamente en función de la página y el límite, en lugar de seguir un patrón continuo.
Tom
3
Hablo más detenidamente
Rick James
17

MySQL no puede ir directamente al registro número 10000 (o al byte 80000 como sugiere) porque no puede asumir que está empaquetado / ordenado de esa manera (o que tiene valores continuos de 1 a 10000). Aunque podría ser así en la actualidad, MySQL no puede asumir que no hay agujeros / huecos / identificadores eliminados.

Entonces, como se señaló en bobs, MySQL tendrá que buscar 10000 filas (o atravesar la entrada número 10000 del índice id) antes de encontrar las 30 para regresar.

EDITAR : para ilustrar mi punto

Tenga en cuenta que aunque

SELECT * FROM large ORDER BY id LIMIT 10000, 30 

sería lento (er) ,

SELECT * FROM large WHERE id >  10000 ORDER BY id LIMIT 30 

sería rápido (er) y devolvería los mismos resultados siempre que no falten ids (es decir, huecos).

Riedsio
fuente
2
Esto es correcto. Pero dado que está limitado por "id", ¿por qué tarda tanto cuando ese id está dentro de un índice (clave principal)? El optimizador debe hacer referencia a ese índice directamente, y luego buscar las filas con identificadores coincidentes (que provienen de ese índice)
Rahman
1
Si usó una cláusula WHERE en la identificación, podría ir directamente a esa marca. Sin embargo, si le pone un límite, ordenado por id, es solo un contador relativo al principio, por lo que tiene que atravesar todo el camino.
Riedsio
Muy buen artículo eversql.com/…
Pažout
Trabajó para mí @Riedsio Gracias.
mahesh kajale
8

Encontré un ejemplo interesante para optimizar las consultas SELECT ORDER BY id LIMIT X, Y. Tengo 35 millones de filas, por lo que me tomó como 2 minutos encontrar un rango de filas.

Aquí está el truco:

select id, name, address, phone
FROM customers
WHERE id > 990
ORDER BY id LIMIT 1000;

Simplemente ponga el DONDE con la última identificación que obtuvo para aumentar mucho el rendimiento. Para mí fue de 2 minutos a 1 segundo :)

Otros trucos interesantes aquí: http://www.iheavy.com/2013/06/19/3-ways-to-optimize-for-paging-in-mysql/

Funciona también con cuerdas.

sym
fuente
1
esto funciona solo para tablas, donde no se eliminan datos
miro
1
@miro Eso solo es cierto si está trabajando bajo el supuesto de que su consulta puede hacer búsquedas en páginas aleatorias, lo cual no creo que este póster asuma. Si bien no me gusta este método para la mayoría de los casos del mundo real, funcionará con lagunas siempre y cuando siempre se base en la última identificación obtenida.
Gremio
5

La parte que lleva mucho tiempo de las dos consultas es recuperar las filas de la tabla. Lógicamente hablando, en la LIMIT 0, 30versión, solo se necesitan recuperar 30 filas. En la LIMIT 10000, 30versión, se evalúan 10000 filas y se devuelven 30 filas. Puede haber alguna optimización en el proceso de lectura de datos, pero considere lo siguiente:

¿Qué pasaría si tuviera una cláusula WHERE en las consultas? El motor debe devolver todas las filas que califican, y luego ordenar los datos, y finalmente obtener las 30 filas.

Considere también el caso en el que las filas no se procesan en la secuencia ORDER BY. Todas las filas que califican deben clasificarse para determinar qué filas devolver.

bobs
fuente
1
solo me pregunto por qué consume tiempo buscar esas 10000 filas. El índice utilizado en ese campo (id, que es una clave principal) debería hacer que recuperar esas filas sea tan rápido como buscar ese índice PK para el registro no. 10000, que a su vez se supone que es rápido como buscar el archivo en ese desplazamiento multiplicado por la longitud del registro de índice, (es decir, buscar 10000 * 8 = byte no 80000, dado que 8 es la longitud del registro de índice)
Rahman
@Rahman: la única forma de contar más allá de las 10000 filas es pasarlas una por una. Esto puede implicar solo un índice, pero aún así las filas de índice tardan en pasar. No existe una estructura MyISAM o InnoDB que pueda "buscar" correctamente (en todos los casos) grabar 10000. La sugerencia 10000 * 8 asume (1) MyISAM, (2) registro de longitud FIJA y (3) nunca elimina de la tabla . De todos modos, los índices MyISAM son BTrees, por lo que no funcionaría.
Rick James
Como dijo esta respuesta, creo que la parte realmente lenta es la búsqueda de filas, no atravesar los índices (que, por supuesto, también se sumarán, pero no tanto como las búsquedas de filas en el disco). Según las consultas de solución proporcionadas para este problema, creo que las búsquedas de filas tienden a suceder si selecciona columnas fuera del índice, incluso si no forman parte de la cláusula order by o where. No he encontrado una razón por la cual esto es necesario, pero parece ser por qué algunas de las soluciones alternativas ayudan.
Gremio
1

Para aquellos que estén interesados ​​en una comparación y cifras :)

Experimento 1: el conjunto de datos contiene aproximadamente 100 millones de filas. Cada fila contiene varios BIGINT, TINYINT, así como dos campos TEXT (deliberadamente) que contienen aproximadamente 1k caracteres.

  • Azul: = SELECT * FROM post ORDER BY id LIMIT {offset}, 5
  • Naranja: = Método @ Quassnoi. SELECT t.* FROM (SELECT id FROM post ORDER BY id LIMIT {offset}, 5) AS q JOIN post t ON t.id = q.id
  • Por supuesto, el tercer método ... WHERE id>xxx LIMIT 0,5no aparece aquí, ya que debería ser un tiempo constante.

Experimento 2: algo similar, excepto que una fila solo tiene 3 BIGINT.

  • verde: = el azul antes
  • rojo: = la naranja antes

ingrese la descripción de la imagen aquí

ch271828n
fuente