Postura correta para classificação aleatória do MySQL

Existe uma estrutura de tabela:
CREATE TABLE `words` (
  ` id` int (11) NOT NULL AUTO_INCREMENT,
  `word` varchar (64) DEFAULT NULL,
  PRIMARY KEY (` id`)
) ENGINE = InnoDB;

Existem 10.000 linhas inseridas na tabela, e 3 palavras são selecionadas aleatoriamente delas.

A maneira mais fácil

mysql> selecione a palavra da ordem das palavras por rand () limite 3;

Embora essa instrução SQL seja muito simples, o processo de execução é um pouco complicado.

O campo Extra exibe Usando temporário e Usando filesort, indicando que uma tabela temporária é necessária e a classificação é necessária na tabela temporária. Para tabelas InnoDB, realizar a classificação de campo completo reduzirá o acesso ao disco, portanto, será preferível.

No entanto, para a tabela de memória, o processo de retorno à tabela simplesmente acessa a memória para obter os dados com base na localização da linha de dados, o que não levará a acesso múltiplo ao disco. O MySQL escolherá a classificação de rowid neste momento .

O fluxo de execução desta instrução é assim:

  1. Crie uma tabela temporária. O mecanismo de memória é usado. Existem dois campos na tabela. O primeiro campo é do tipo double e está marcado como campo R, e o segundo campo é do tipo varchar (64) e é marcado como campo W. Além disso, esta tabela não está indexada.
  2. Da tabela de palavras, retire todos os valores das palavras na ordem da chave primária. Para cada valor de palavra, chame a função rand () para gerar um decimal aleatório maior que 0 e menor que 1, e armazene este decimal aleatório e palavra nos campos R e W da tabela temporária respectivamente. Até agora, o número de digitalizados linhas é 10.000 .
  3. Agora a tabela temporária tem 10.000 linhas de dados. Em seguida, você precisa classificar pelo campo R nesta tabela temporária de memória não indexada.
  4. Inicialize sort_buffer. Existem dois campos em sort_buffer, um é do tipo duplo e o outro é do tipo inteiro.
  5. Busque o valor R e as informações de localização linha por linha da tabela temporária de memória e armazene-os em dois campos em sort_buffer. Este processo requer uma verificação completa da tabela. Neste momento, o número de linhas verificadas aumenta em 10.000 e se torna 20.000.
  6. Classifique de acordo com o valor de R em sort_buffer. Observe que esse processo não envolve operações de tabela, portanto, não aumentará o número de linhas verificadas.
  7. Após a classificação ser concluída, as informações de localização dos três primeiros resultados são recuperadas, o valor da palavra é recuperado da tabela de memória temporária por sua vez e devolvido ao cliente. Nesse processo, as três linhas de dados da tabela são acessadas e o número total de linhas varridas passa a 2.0003 .

Nota: Qual é o conceito de "informações de localização" na etapa 5: o mecanismo MEMORY não é uma tabela organizada por índice. Neste exemplo, você pode pensar nisso como um array. Portanto, este rowid é na verdade o subscrito do array.

Use o log 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 uma tabela temporária de memória, e o método de classificação rowid é usado ao classificar a tabela temporária de memória.

tmp_table_size Esta configuração limita o tamanho da tabela temporária de memória, o valor padrão é 16M. Se o tamanho da tabela temporária exceder tmp_table_size, a tabela temporária na memória será convertida em uma tabela temporária de disco . O mecanismo padrão usado para tabelas temporárias de disco é InnoDB, que é controlado pelo parâmetro internal_tmp_disk_storage_engine .

Ao usar tabelas temporárias de disco, o exemplo acima corresponde ao processo de classificação de uma tabela InnoDB sem um í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

    

 O sort_mode mostra a classificação rowid e as linhas que estão envolvidas na classificação são o campo de valor aleatório R e o campo rowid.

O valor aleatório armazenado no campo R é de apenas 8 bytes, rowid é de 6 bytes e o número total de linhas de dados é 10000. Isso é 140000 bytes, que excede os 32.768 bytes definidos por sort_buffer_size. No entanto, o valor de number_of_tmp_files é realmente 0. Porque a classificação desta instrução SQL é um novo algoritmo de classificação introduzido pela versão 5.6 do MySQL, a saber: algoritmo de classificação de fila de prioridade. A partir do resultado OPTIMIZER_TRACE, a parte escolhida = true de filesort_priority_queue_optimization também pode ser vista.

Na verdade, nossa instrução SQL atual só precisa pegar os 3 rowids com o menor valor de R. Se o algoritmo de classificação por mesclagem for usado, embora os 3 primeiros valores possam ser obtidos no final, esse algoritmo classificará todas as 10.000 linhas de dados. É desnecessário.

O algoritmo de fila de prioridade pode obter com precisão apenas três valores mínimos. O processo de execução é o seguinte:

  1. Para que 10.000 (R, rowid) sejam classificados, primeiro pegue as três primeiras linhas e construa um heap;
  2. Pegue a próxima linha (R ', rowid') e compare-a com o maior R no heap atual. Se R'for menor que R, remova este (R, rowid) do heap e substitua-o por (R ', rowid ');
  3. Repita a etapa 2 até que o 10000 (R ', rowid') seja comparado.

A consulta SQL no artigo anterior também tem o limite de 1000. Se o algoritmo de fila de prioridade for usado, o tamanho do heap que precisa ser mantido é de 1000 linhas (nome, rowid), que excede o tamanho de sort_buffer_size que eu defini, então eu só pode usar o algoritmo de classificação por mesclagem.

Resumindo, não importa qual tipo de tabela temporária é usada, a ordem por rand () tornará o processo de cálculo muito complicado e exigirá um grande número de linhas de varredura, portanto, o consumo de recursos do processo de classificação será muito grande.

Classifique corretamente de forma aleatória

Simplifique o problema primeiro, se apenas um valor de palavra for selecionado aleatoriamente:

  1. Obtenha o valor máximo M e o valor mínimo N do id da chave primária desta tabela;
  2. Use uma função aleatória para gerar um número entre o máximo e o mínimo X = (MN) * rand () + N;
  3. Pegue a linha com o primeiro ID não inferior a X.

Por enquanto chamado de algoritmo aleatório 1, observe a sequência de instruções de execução:

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 é muito eficiente, porque max (id) e min (id) não precisam varrer o índice, e a terceira etapa da seleção também pode usar o índice para localizar rapidamente, o que pode ser considerado para varrer apenas 3 linhas. Mas, na verdade, esse algoritmo em si não atende estritamente aos requisitos aleatórios do título, porque pode haver lacunas no ID, então a probabilidade de escolher linhas diferentes é diferente , não verdadeiramente aleatória.

Para obter resultados estritamente aleatórios, você pode usar o seguinte processo:

  1. Obtenha o número de linhas em toda a tabela e registre-o como C.
  2. Obtenha Y = floor (C * rand ()). O papel da função de chão aqui é assumir a parte inteira.
  3. Use o limite Y, 1 para obter uma linha.

Este é o algoritmo aleatório 2, que resolve o problema óbvio de probabilidade desigual no algoritmo 1. A abordagem do MySQL para processar o limite Y, 1 é lê-los um por um em ordem, descartar o primeiro Y e, em seguida, usar o próximo registro como o resultado de retorno , portanto, esta etapa precisa verificar Y + 1 linhas. Além disso, a linha C varrida na primeira etapa requer a varredura C + Y + 1 linhas no total, e o custo de execução é maior que o custo do algoritmo aleatório 1.

Se calculado de acordo com esta tabela com 10.000 linhas, C = 10.000, se for aleatório para um valor Y maior, o número de linhas verificadas é quase 20000, que é próximo ao número de linhas verificadas de ordem por rand (), mas ainda mais do que ordenar por rand () é muito menos caro de executar. Como o algoritmo aleatório 2 executa o limite para obter dados de acordo com a classificação da chave primária e a classificação natural do índice da chave primária, esse processo é omitido aqui.

Se seguirmos a ideia do algoritmo aleatório 2, precisamos selecionar aleatoriamente 3 valores de palavras:

  1. Obtenha o número de linhas em toda a tabela, denotado como C;
  2. Obtenha Y1, Y2, Y3 de acordo com o mesmo método aleatório;
  3. Execute três instruções de limite Y, 1 para obter três linhas de dados.

O número total de linhas de varredura deste algoritmo aleatório é C + (Y1 + 1) + (Y2 + 1) + (Y3 + 1). Na verdade, ele pode continuar a ser otimizado para reduzir ainda mais o número de linhas de varredura:

  1. Depois de randomizar Y1, Y2, Y3, calcule Ymax e Ymin;
  2. 再用 selecione id do limite t Ymin , (Ymax - Ymin + 1) ;
  3. Depois de obter o conjunto de id, calcule os três ids correspondentes a Y1, Y2 e Y3;
  4. 最后 selecione * de t onde id em (id1, id2, id3)。

O número de linhas escaneadas desta forma deve ser C + Ymax + 3.

 

 

Fonte do conteúdo: Lin Xiaobin "45 Lectures on MySQL Actual Combat"

Acho que você gosta

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