Acabei de criar meu primeiro banco de dados e tabela MySQL, mas fiquei surpreso com o desempenho lento de uma simples instrução select. Minha tabela tem 400 milhões de linhas e minha instrução select retorna cerca de 100.000 linhas, mas demorou 14 minutos! Não tenho certeza se minha configuração está errada ou se minhas expectativas em relação ao mysql eram muito altas. Qual seria o tempo esperado para uma tabela bem projetada retornar 100.000 linhas de uma tabela de 400 milhões de linhas? Esta é a minha configuração:
CREATE TABLE CALLS (
quote_date DATE,
quote_time TIME,
expiration DATE,
delta decimal(4,3),
mid decimal(8,4)
);
CREATE INDEX idx_quote_date ON CALLS (quote_date);
CREATE INDEX idx_quote_time ON CALLS (quote_time);
CREATE INDEX idx_expiration ON CALLS (expiration);
CREATE INDEX idx_delta on CALLS (delta);
CREATE INDEX covering_index ON CALLS (quote_date, quote_time, expiration, delta);
Minha tabela serve apenas para ler dados com instruções select, então faço os índices depois de carregar todos os dados.
Minha seleção é:
select * from CALLS where DELTA BETWEEN 0.4 and 0.6;
P: O que o plano de consulta mostra?
R: Use
EXPLAIN ANALYZE
para ver.Meu palpite é que ele está verificando uma grande parte, senão toda a tabela, para localizar seus dados. Isso ocorre devido aos seguintes problemas:
SELECT *
which é um antipadrão.Maneiras de melhorar seu caso de teste:
SELECT *
e, em vez disso, liste explicitamente as colunas que deseja selecionar.CREATE INDEX idx_delta_expiration_mid on CALLS (delta, expiration, mid);
se sua consulta forSELECT delta, expiration, mid FROM CALLS where DELTA BETWEEN 0.4 and 0.6;
, por exemplo. Isso tornará o índice aplicável para uso, de preferência com a busca de localizar com eficiência os dados de seu interesse, em vez de verificar a tabela.Essa consulta provavelmente usará
INDEX(delta)
o que você possui. Será realizado da seguinte forma (pseudocódigo):As idas e vindas em "2a" são bastante caras, especialmente se a mesa for maior que
innodb_buffer_pool_size
.É perverso não especificar um
PRIMARY KEY
; um foi fornecido para você. (Este detalhe não afeta o desempenho.)Seu "covering_index" não está cobrindo (para esta consulta) porque
mid
está faltando.A ordem das colunas em um índice composto é importante. (Mas não neste exemplo.)
Se você não precisar de todas as colunas, seria mais rápido ter um índice composto começando com delta (para que fosse usado) e contendo todas as colunas necessárias (cobertura). (Como JD aponta.)
Mais sobre indexação: Index Cookbook
Observações gerais:
Onde está seu
PRIMARY KEY
?Parece-me que esses dados são imutáveis? Com isso quero dizer que um dado
call
é uma questão de registro histórico, em vez de estar sujeito a múltiplas atualizações.A partir daqui , obtemos (advertência - não tenho certeza se é com isso que estamos lidando):
Você não tem nenhum identificador de estoque - isso me intriga - certamente você deveria ter um campo como
identifier CHAR(4)
- ou qualquer que seja o comprimento dos identificadores do seu sistema - se for variável, useVARCHAR(n)
- storage = n + 1 byte.Você pode armazenar
DATE
eTIME
juntos como umTIMESTAMP
, que pode conter valores entre '1970-01-01 00:00:01' (UTC) e '2038-01-19 03:14:07' (UTC). Se você não precisar de mais de 1s de precisão, isso economizará espaço - 4 bytes versus 6.Além disso, quanto ao seu PK, a partir daqui , você não precisa de um substituto
PRIMARY KEY
(normalmenteINTEGER
) se seus dados não estiverem mudando muito, o que, como supus acima, deveria ser o caso do que é, essencialmente, uma tabela de registro.1ª possibilidade:
Então, eu projetaria assim (todo o código abaixo está disponível no violino aqui ):
Você pode ter um índice
delta
da seguinte maneira:Observe que se você fizer isso, você receberá um
INDEX SCAN
erro ao executar sua consulta (usandoEXPLAIN ANALYZE
):2ª possibilidade:
Agora, se a expiração for determinada pelo número de dias a partir do
quote_ts
, você poderia armazená-lo comoSMALLINT
(ou mesmoUNSIGNED TINYINT
se nunca for > 255 dias) e usar aDATE_ADD()
função e terexpiry_date
como campo armazenado (VIRTUAL
) - sem espaço, o cálculo é feito no voar. Veja o violino - teste com seu próprio sistema.Em seguida, crie o mesmo índice
delta
e execute novamente a consulta -INDEX SCAN
novamente. Experimente algumas consultas diferentes - verifique os tamanhos das tabelas (+/-INDEX
es.Há uma certa sobrecarga associada a cada registro - os registros são armazenados em páginas (novamente, sobrecarga), assim como
INDEX
es - mais sobrecarga. Portanto, para controlar seu uso/registro real de dados, você teria que carregar 1 milhão de registros e realizar as consultas na parte inferior do violino.Alguns pontos a serem observados:
Não sei por que você está recuperando 400 mil registros de uma tabela - você está agregando alguma coisa?
Não sei como os campos delta e intermediário são produzidos/calculados/derivados. Se puderem
GENERATED
, há potencial para reduzir o tamanho da sua tabela - e, portanto, o tempo necessário para verificá-la?É melhor ter
NOT NULL
tantos campos quanto possível - quanto mais informações você fornecer ao otimizador, maiores serão as chances de ele fazer um bom trabalho. Talvez isso não seja possível para omid
campo que não pode ser inserido até acall
metade do seu prazo? (não sei muito sobre negociação).Seus dois índices (
CREATE INDEX covering_index ON CALLS (quote_date, quote_time, expiration, delta);
eCREATE INDEX covering_index ON CALLS (quote_date)
podem ser substituídos peloPRIMARY KEY
- o primeiro campo de aPK
não precisa de um extra,INDEX
pois é o campo inicial de qualquer maneira.Em vez do
BETWEEN
operador, é melhor usar os matemáticos (<
,<=
,>=
,>
) - estes são inequívocos - sãoBETWEEN
inclusivos e exclusivos? A maioria dos servidores simplesmente transformará esse operador nesses outros operadores - veja aqui para confusão!Com o índice
delta_ix
, ele é usado em uma consulta com registros de solicitações baseadas em valores dedelta
- vejaEXPLAIN ANALYZE
no violino.A ordem dos campos deve
PK
depender da sua consulta ou consultas mais frequentes. Teste para isso.Se você quiser explicar melhor como os campos são produzidos, talvez tenhamos mais oportunidades de reduzir o tamanho do registro e agilizar suas consultas. ps. bem-vindo ao dba.se!
Supondo que a coluna "delta" seja distribuída aleatoriamente, sua consulta está selecionando 100 mil linhas aleatórias da tabela de 400 milhões de linhas.
Existe um índice no delta, mas ele ainda terá que ler 100 mil linhas distribuídas aleatoriamente da tabela.
14min/100k = 8,4 milissegundos por linha, o que é muito próximo do tempo de acesso aleatório de um disco rígido de 7200rpm... hmm...
Portanto, acho que você está executando isso em um disco rígido de 7200 rpm. Vai demorar um pouco, não há como evitar. A cabeça da unidade precisa se mover para alcançar os dados.
As únicas soluções são
Use um SSD NVME rápido com alto IOPS aleatório ou coloque RAM suficiente na caixa para manter toda a tabela em cache.
Ou use um índice de cobertura em (delta, as demais colunas), que transformará o acesso aleatório em sequencial que é muito mais rápido. Mas vai ocupar muito espaço e demorar um pouco para ser construído.
Outra solução é utilizar um banco de dados especializado nesse tipo de coisa:
Não há índice na coluna "valor", porque são dados MQTT, então o índice está ativado (mqtt_topic, timestamp), que também é a ordem da tabela e a chave de particionamento. Assim, ele lê a tabela inteira. Clickhouse compacta essa tabela de 2,1 bilhões de linhas (38 GB) por um fator de cerca de 11, portanto, usa apenas 3,4 GB, que o SSD NVME lê em cerca de 1 segundo, e a consulta é concluída em 2,7s em um PC desktop barato. Isso não inclui o tempo para transportar o conjunto de resultados para o cliente; nesse caso, o conjunto de resultados tem 1,6 milhão de linhas, o que seria cerca de 50 MB, meio segundo em Ethernet gigabit.