Por que o deslocamento LIMIT mais alto do MYSQL atrasa a consulta?


173

Cenário resumido: uma tabela com mais de 16 milhões de registros [2 GB de tamanho]. Quanto maior o deslocamento de LIMIT com SELECT, mais lenta a consulta se torna, ao usar ORDER BY * primary_key *

assim

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

leva muito menos do que

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

Isso só encomenda 30 registros e mesmo assim. Portanto, não é a sobrecarga de ORDER BY.
Agora, ao buscar as últimas 30 linhas, leva cerca de 180 segundos. Como posso otimizar essa consulta simples?


NOTA: Eu sou o autor. O MySQL não se refere ao índice (PRIMARY) nos casos acima. veja o link abaixo pelo usuário "Quassnoi" para explicação.
Rahman

Respostas:


197

É normal que compensações mais altas atrasem a consulta, pois a consulta precisa contar os primeiros OFFSET + LIMITregistros (e tirar apenas LIMITdeles). Quanto maior esse valor, mais a consulta é executada.

A consulta não pode ir diretamente para OFFSET, porque, primeiro, os registros podem ter comprimentos diferentes e, segundo, pode haver falhas nos registros excluídos. Ele precisa verificar e contar cada registro a caminho.

Assumindo que idé um PRIMARY KEYde uma MyISAMtabela, você pode acelerá-lo usando este truque:

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

Veja este artigo:


7
O comportamento de "pesquisa de linha inicial" do MySQL foi a resposta por que está falando há tanto tempo. Pelo truque que você forneceu, apenas os IDs correspondentes (diretamente pelo índice) são vinculados, salvando pesquisas de linha desnecessárias de muitos registros. Isso fez o truque, viva!
Rahman

4
@ harald: o que exatamente você quer dizer com "não funciona"? Esta é uma pura melhoria de desempenho. Se não houver um índice utilizável ORDER BYou o índice cobrir todos os campos necessários, você não precisará dessa solução alternativa.
Quassnoi

6
@ F055: a resposta diz "acelerar", não "tornar instantâneo". Você leu a primeira frase da resposta?
Quassnoi

3
É possível executar algo parecido com isto para o InnoDB?
NeverEndingQueue

3
@ Lanti: envie-o como uma pergunta separada e não se esqueça de marcar com ele postgresql. Esta é uma resposta específica do MySQL.
Quassnoi

220

Eu mesmo tive o mesmo problema. Como você deseja coletar uma grande quantidade desses dados e não um conjunto específico de 30, provavelmente você estará executando um loop e incrementando o deslocamento em 30.

Então, o que você pode fazer é:

  1. Mantenha o último ID de um conjunto de dados (30) (por exemplo, lastId = 530)
  2. Adicione a condição WHERE id > lastId limit 0,30

Assim, você sempre pode ter um deslocamento ZERO. Você ficará surpreso com a melhoria de desempenho.


Isso funciona se houver lacunas? E se você não tiver uma única chave exclusiva (uma chave composta, por exemplo)?
Xaisoft

8
Pode não ser óbvio para todos que isso só funciona se o conjunto de resultados for classificado por essa chave, em ordem crescente (por ordem decrescente, a mesma idéia funciona, mas mude> lastid para <lastid.) Não importa se é o chave primária ou outro campo (ou grupo de campos).
Eloff 16/09

Muito bem, esse homem! Uma solução muito simples que resolveu meu problema :-)
oodavid

30
Apenas uma observação de que o limite / deslocamento é frequentemente usado em resultados paginados e manter lastId simplesmente não é possível porque o usuário pode pular para qualquer página, nem sempre para a próxima página. Em outras palavras, o deslocamento geralmente precisa ser calculado dinamicamente com base na página e no limite, em vez de seguir um padrão contínuo.
28413 Tom

3
Eu falo mais detalhadamente sobre "lembrar de onde você parou" em mysql.rjweb.org/doc.php/pagination
Rick James

17

O MySQL não pode ir diretamente para o 10000º registro (ou o 80000º byte como sugerido) porque ele não pode assumir que ele foi compactado / ordenado dessa maneira (ou que possui valores contínuos em 1 a 10000). Embora possa ser assim na realidade, o MySQL não pode assumir que não há buracos / lacunas / IDs excluídos.

Portanto, como observou o bobs, o MySQL precisará buscar 10000 linhas (ou percorrer as 10000ª entradas do índice id) antes de encontrar as 30 para retornar.

EDIT : Para ilustrar o meu ponto

Observe que, embora

SELECT * FROM large ORDER BY id LIMIT 10000, 30 

seria lento (er) ,

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

seria rápido (er) e retornaria os mesmos resultados, desde que não houvesse ids ausentes (ou seja, lacunas).


2
Isto está certo. Mas como é limitado por "id", por que demora tanto tempo quando esse id está dentro de um índice (chave primária)? O otimizador deve se referir diretamente a esse índice e, em seguida, buscar as linhas com os IDs correspondentes (que vieram desse índice)
Rahman

1
Se você usou uma cláusula WHERE no id, ela pode ir diretamente para essa marca. No entanto, se você colocar um limite, ordenado por ID, é apenas um contador relativo ao início, portanto, ele deve se mover o tempo todo.
Riedsio

Artigo muito bom eversql.com/…
Pažout

Trabalhou para mim @Riedsio Obrigado.
Mahesh kajale # 9/18

8

Encontrei um exemplo interessante para otimizar consultas SELECT ORDER BY id LIMIT X, Y. Eu tenho 35 milhões de linhas, então demorei 2 minutos para encontrar um intervalo de linhas.

Aqui está o truque:

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

Basta colocar WHERE com o último id que você conseguiu aumentar muito o desempenho. Para mim, foi de 2 minutos a 1 segundo :)

Outros truques interessantes aqui: http://www.iheavy.com/2013/06/19/3-ways-to-optimize-for-paging-in-mysql/

Também funciona com strings


1
isso funciona somente para tabelas, onde há dados são apagados
miro

1
@miro Isso só é verdade se você estiver trabalhando com o pressuposto de que sua consulta pode fazer pesquisas em páginas aleatórias, o que não acredito que este pôster esteja assumindo. Embora eu não goste desse método para a maioria dos casos do mundo real, isso funcionará com lacunas, desde que você esteja sempre baseando-o no último ID obtido.
Grêmio

5

A parte demorada das duas consultas é recuperar as linhas da tabela. Logicamente falando, na LIMIT 0, 30versão, apenas 30 linhas precisam ser recuperadas. Na LIMIT 10000, 30versão, 10000 linhas são avaliadas e 30 linhas são retornadas. Pode haver alguma otimização no processo de leitura de dados, mas considere o seguinte:

E se você tivesse uma cláusula WHERE nas consultas? O mecanismo deve retornar todas as linhas qualificadas e, em seguida, classificar os dados e, finalmente, obter as 30 linhas.

Considere também o caso em que as linhas não são processadas na sequência ORDER BY. Todas as linhas qualificadas devem ser classificadas para determinar quais linhas retornar.


1
apenas imaginando por que consome tempo para buscar essas 10000 linhas. O índice usado nesse campo (id, que é uma chave primária) deve recuperar essas linhas tão rapidamente quanto buscar o índice PK para o registro no. 10000, que por sua vez deve ser rápido como buscar o arquivo nesse deslocamento multiplicado pelo comprimento do registro de índice (ou seja, buscar 10000 * 8 = byte no 80000 - considerando que 8 é o comprimento do registro de índice)
Rahman

@Rahman - A única maneira de contar além das 10.000 linhas é passar por elas uma a uma. Isso pode envolver apenas um índice, mas ainda assim as linhas do índice levam um tempo para serem percorridas. Não existe uma estrutura MyISAM ou InnoDB que possa (corretamente) (em todos os casos) "procurar" registrar 10000. A sugestão 10000 * 8 assume (1) MyISAM, (2) registro de comprimento FIXO e (3) nunca exclui da tabela . De qualquer forma, os índices MyISAM são BTrees, portanto, não funcionaria.
Rick James

Como essa resposta afirmou, acredito que a parte mais lenta é a pesquisa de linha, não percorrendo os índices (que, obviamente, também serão adicionados, mas nem de longe as pesquisas de linha no disco). Com base nas consultas alternativas fornecidas para esse problema, acredito que as pesquisas de linha tendem a ocorrer se você estiver selecionando colunas fora do índice - mesmo que não façam parte da cláusula order or where where. Não encontrei um motivo para isso ser necessário, mas parece que algumas das soluções alternativas ajudam.
Grêmio

1

Para aqueles que estão interessados ​​em uma comparação e figuras :)

Experiência 1: o conjunto de dados contém cerca de 100 milhões de linhas. Cada linha contém vários campos BIGINT, TINYINT, bem como dois campos de texto (deliberadamente) contendo cerca de 1k caracteres.

  • Azul: = SELECT * FROM post ORDER BY id LIMIT {offset}, 5
  • Laranja: = @ método de Quassnoi. SELECT t.* FROM (SELECT id FROM post ORDER BY id LIMIT {offset}, 5) AS q JOIN post t ON t.id = q.id
  • Obviamente, o terceiro método ... WHERE id>xxx LIMIT 0,5,, não aparece aqui, pois deve ser tempo constante.

Experiência 2: Coisa semelhante, exceto que uma linha possui apenas 3 BIGINTs.

  • verde: = o azul antes
  • vermelho: = a laranja antes

insira a descrição da imagem aqui

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.