Por que o InnoDB não armazena a contagem de linhas?

19

Todo mundo sabe que, em tabelas que usam o InnoDB como mecanismo, consultas como SELECT COUNT(*) FROM mytablesão muito inexatas e muito lentas, especialmente quando a tabela fica maior e há inserções / exclusões constantes de linhas enquanto essa consulta é executada.

Pelo que entendi, o InnoDB não armazena a contagem de linhas em uma variável interna, que é a razão desse problema.

Minha pergunta é: por que isso acontece? Seria tão difícil armazenar essas informações? É uma informação importante para saber em tantas situações. A única dificuldade que vejo se uma contagem interna seria implementada é quando as transações estão envolvidas: se a transação não for confirmada, você conta as linhas inseridas por ela ou não?

PS: Eu não sou especialista em DBs, sou apenas alguém que tem o MySQL como um hobby simples. Portanto, se eu apenas pedir algo estúpido, não seja excessivamente crítico: D.

Radu Murzea
fonte
6
Devagar, sim. Inexato, não. É lento porque fornece o resultado exato. Quando você tem uma tabela de 200 milhões de linhas e, possivelmente, muitas outras transações que inserem / excluem na mesma tabela, possivelmente muitas linhas por segundo, outra pergunta é "você precisa do número exato?"
ypercubeᵀᴹ
@ypercube Eu sei que vi algumas vezes no phpmyadmin alguns valores de contagem de linhas que estavam muito desativados. Além disso, há um comentário dizendo algo como "pode ​​não ser preciso".
Radu Murzea
1
Os usuários do @RaduMurzea phpMyAdmin usam um método alternativo para calcular as contagens de tabelas do InnoDB pelas razões de velocidade que você conhece. É aqui que a imprecisão mencionada entra em jogo. As SELECT COUNT(*) FROM ...consultas reais são precisas. Se preferir, o phpMyAdmin pode ser configurado para sempre usar contagens exatas de linhas à custa da velocidade. Mais informações: stackoverflow.com/questions/11926259/...
DOOManiac

Respostas:

9

Concordo com @RemusRusanu (+1 para sua resposta)

SELECT COUNT(*) FROM mydb.mytableno InnoDB se comporta como um mecanismo de armazenamento transacional deveria. Compare com o MyISAM.

MyISAM

Se mydb.mytablefor uma tabela MyISAM, iniciar SELECT COUNT(*) FROM mydb.mytable;é como executar SELECT table_rows FROM information_schema.table WHERE table_schema = 'mydb' AND table_name = 'mytable';. Isso aciona uma pesquisa rápida da contagem de linhas no cabeçalho da tabela MyISAM.

InnoDB

Se mydb.mytablefor uma tabela do InnoDB, você terá uma mistura de coisas acontecendo. Você tem o MVCC em andamento, governando o seguinte:

  • ib_logfile0 / ib_logfile1 (refazer logs)
  • ibdata1
    • Desfazer logs
    • Reversões
    • Alterações no dicionário de dados
  • Gerenciamento de buffer pool
  • Isolamento de transação (4 tipos)
    • Leituras Repetíveis
    • Leitura confirmada
    • Leitura não confirmada
    • Serializable

Pedir uma contagem de tabelas ao InnoDB requer navegação por essas coisas ameaçadoras. De fato, nunca se sabe realmente se SELECT COUNT(*) from mydb.mytableconta apenas leituras repetíveis ou inclui leituras que foram confirmadas e não confirmadas.

Você pode tentar estabilizar um pouco as coisas ativando innodb_stats_on_metadata .

De acordo com a documentação do MySQL em innodb_stats_on_meta_data

Quando essa variável é ativada (que é o padrão, como antes da criação da variável), o InnoDB atualiza as estatísticas durante instruções de metadados, como SHOW TABLE STATUS ou SHOW INDEX, ou ao acessar as tabelas INFORMATION_SCHEMA TABLES ou STATISTICS. (Essas atualizações são semelhantes ao que acontece com o ANALYZE TABLE.) Quando desativado, o InnoDB não atualiza as estatísticas durante essas operações. Desabilitar essa variável pode melhorar a velocidade de acesso para esquemas que possuem um grande número de tabelas ou índices. Também pode melhorar a estabilidade dos planos de execução para consultas que envolvem tabelas do InnoDB.

Desativá-lo pode ou não fornecer uma contagem mais estável em termos de configuração de planos EXPLAIN. Isso pode afetar o desempenho de SELECT COUNT(*) from mydb.mytableuma maneira boa, ruim ou de maneira alguma. Experimente e veja !!!

RolandoMySQLDBA
fonte
16

Para iniciantes, não existe a "contagem atual" para armazenar em uma variável. Uma consulta como SELECT COUNT(*) FROM ...está sujeita ao nível de isolamento atual e a todas as transações pendentes simultâneas. Dependendo do nível de isolamento, a consulta pode ver ou não ver linhas inseridas ou excluídas por transações não confirmadas pendentes. A única maneira de responder é contar as linhas que são visíveis para a transação atual.

Observe que eu nem toquei no assunto ainda mais espinhoso das transações simultâneas que começam ou terminam durante a contagem. Sem mencionar reversões ...

Remus Rusanu
fonte
1
Ok, então depende do nível de isolamento, isso faz sentido. Mas ainda pode ser implementado.
Radu Murzea
@SoboLAN Existem muitas razões pelas quais não deveria e não pode ser, a maioria das quais estão listadas acima. Você o implementaria mantendo uma lista de contagens por tabela por transação iniciada (qualquer que seja o SCN da Oracle no MySQL)? Gerenciar essas contagens seria uma sobrecarga massiva - pense em um banco de dados com centenas ou milhares de sessões simultâneas, cada uma executando grandes quantidades de INSERTs / DELETEs na mesma tabela. Impossível de manter.
Philᵀᴹ
Implementar isso é bastante difícil. Apenas pense que a contagem precisa persistir no banco de dados, ou seja, em algum lugar dos metadados, e essa contagem deve ser mantida por toda transação que insere ou exclui uma linha. Como você bloquearia esses metadados? E como você lidaria com reversões? Está longe de ser trivial. E o resultado seria utilizável para um subconjunto muito restrito de consultas.
Remus Rusanu
3
@JackDouglas Interesting. Pelo que vi no passado, as COUNT(*)consultas raramente são necessárias na realidade e geralmente resultam de inexperiência do desenvolvedor (conte as linhas antes de selecioná-las!) Ou de um design de aplicativo ruim.
Philᵀᴹ
1
@SoboLAN - não, não seria. Ter um serviço que atualiza algum tipo de tabela de estatísticas em intervalos de tempo predefinidos é muito melhor. Imagine ter um banco de dados grande e vários administradores consultando a maioria das tabelas SELECT COUNT(*), adicione um não otimizado WHEREà tabela e você terá alguns usuários trazendo o db de joelhos para vários contadores de estatísticas úteis.
NB
0

Embora teoricamente fosse possível manter uma contagem precisa do número de linhas para uma determinada tabela com o InnoDB, isso custaria muito bloqueio, o que afetaria negativamente o desempenho. Também diferiria com base no nível de isolamento.

O MyISAM já faz o bloqueio no nível da tabela, portanto não há custo extra.

Raramente preciso de uma contagem de linhas para uma tabela, embora use COUNT (*) um pouco. Geralmente, tenho uma cláusula WHERE anexada. Usando um índice eficiente em um pequeno conjunto de resultados, acho que eles são rápidos o suficiente.

Não concordo que as contagens sejam imprecisas. As contagens representam um instantâneo dos dados, e eu sempre os achei exatos.

Em resumo, o MySQL deixa você implementar isso para o InnoDB. Você pode armazenar uma contagem e incrementá-la / diminuí-la após cada consulta. Porém, a solução mais fácil é provavelmente mudar para o MyISAM.

Marcus Adams
fonte
2
É não possível manter uma contagem precisa do de linhas em um sistema transacional. Porque existem tantas contas de linha diferentes (e corretas) quanto transações ativas.
A_horse_with_no_name 16/05
5
Eu dei -1 aqui para 'Embora, a solução mais fácil é provavelmente mudar para o MyISAM.' Eu nunca recomendaria mudar para o MyISAM simplesmente para obter a contagem de linhas.
Derek Downey
@a_horse_with_no_name, você concorda que haveria uma contagem de linhas "correta" para cada transação. Parece possível para mim.
Marcus Adams
1
@ Test, eu nunca disse "simplesmente para obter a contagem de linhas".
Marcus Adams
@a_horse_with_no_name, Isso não parece certo. Certamente, estamos contando apenas o número de linhas quando as transações são confirmadas, certo?
Pacerier