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:
- 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.
- 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 .
- 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.
- Inicialize sort_buffer. Existem dois campos em sort_buffer, um é do tipo duplo e o outro é do tipo inteiro.
- 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.
- 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.
- 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:
- Para que 10.000 (R, rowid) sejam classificados, primeiro pegue as três primeiras linhas e construa um heap;
- 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 ');
- 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:
- Obtenha o valor máximo M e o valor mínimo N do id da chave primária desta tabela;
- Use uma função aleatória para gerar um número entre o máximo e o mínimo X = (MN) * rand () + N;
- 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:
- Obtenha o número de linhas em toda a tabela e registre-o como C.
- Obtenha Y = floor (C * rand ()). O papel da função de chão aqui é assumir a parte inteira.
- 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:
- Obtenha o número de linhas em toda a tabela, denotado como C;
- Obtenha Y1, Y2, Y3 de acordo com o mesmo método aleatório;
- 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:
- Depois de randomizar Y1, Y2, Y3, calcule Ymax e Ymin;
- 再用 selecione id do limite t Ymin , (Ymax - Ymin + 1) ;
- Depois de obter o conjunto de id, calcule os três ids correspondentes a Y1, Y2 e Y3;
- 最后 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"