Tabela A
- auto-incrementing integer primary key
- 3 dates
- 17 booleans
- 7 varchars (less than 40 bytes in these combined varchars in 99% of rows)
- 5 foreign keys
Tabela B
- auto-incrementing integer primary key
- 1 date
- 2 boolean fields
- 3 varchars (1 unique UUID string in each row, other 2 varchars empty 99% of the time)
- 3 foreign keys
- A Tabela A tem 25 milhões de linhas -
SELECT COUNT(*)
leva aproximadamente 70 segundos - A Tabela B tem 20 milhões de linhas -
SELECT COUNT(*)
leva aproximadamente 6-7 segundos
Eu só incluí a Tabela B para ilustrar minha suposição, que é que o 900% mais tempo que leva para contar a Tabela A, apesar de ter apenas 25% mais linhas do que a Tabela B, é que a Tabela A tem 33 colunas, em comparação com apenas 10 colunas na Tabela B. Percebo que 33 colunas não é uma tabela "grande", mas pode ser maior do que precisamos, pois usamos apenas algumas dessas colunas em muitas operações.
Não estamos enfrentando nenhum problema sério, além de contar demorar muito para ser útil dentro de uma solicitação da web (por exemplo, para paginação em uma exibição de lista de administração do Django).
No entanto, estamos prestes a aumentar o tamanho da Tabela A, de 25 milhões, em cerca de 1 a 2 milhões de linhas por mês, devido a um novo recurso, que aumenta nossos temores sobre o desempenho em geral.
Estamos considerando as seguintes opções:
Dividir Tabela A
Colocaríamos a maioria das colunas da Tabela A em uma nova tabela filha (Tabela A Meta) com uma relação de 1 para 1 com a Tabela A. Apenas 4 a 6 colunas mais usadas permaneceriam na Tabela A e, presumivelmente, isso ajudaria faça consultas de leitura e contagem mais rápidas às custas de precisar de uma consulta separada (ou JOIN) para obter os detalhes adicionais de uma determinada linha, bem como uma pequena quantidade de sobrecarga de gravação adicional.
Espere por problemas e particione
A outra opção é ignorar o fato de que SELECT COUNT(*)
é basicamente inutilizável, já que outras coisas estão funcionando corretamente (consultas por meio de campos indexados, etc.), e continuar usando a Tabela A mesmo após 50, 75 milhões de linhas etc., eventualmente particionando em algum momento no futuro.
Sei que essas opções não são mutuamente exclusivas, mas gostaria de obter algum feedback geral sobre essas opções e se você se incomodaria em fazer a primeira etc.
Execução
Aqui está o plano de execução para o mais COUNT
mencionado:
db=# EXPLAIN ANALYZE SELECT COUNT(*) FROM "message";
QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------------
Aggregate (cost=1236008.12..1236008.14 rows=1 width=8) (actual time=79357.273..79357.293 rows=1 loops=1)
-> Seq Scan on message (cost=0.00..1173727.10 rows=24912410 width=0) (actual time=1.460..65375.321 rows=24926666 loops=1)
Planning time: 0.543 ms
Execution time: 79357.350 ms
(4 rows)
count(*)
é sempre lento , então não faça isso a menos que seja necessário.O fator mais importante geralmente é o tamanho físico da mesa, então verifique isso para obter uma explicação para o que você observa.
Dividir a tabela ao longo de um relacionamento de um para um pode acelerar a contagem, mas retardará outras consultas, o que parece um mau negócio.
O particionamento não ajudará, mas feito corretamente, pode facilitar a eliminação de dados antigos. Usar uma versão recente do PostgreSQL e uma consulta paralela com muitos processos é mais promissor para uma contagem rápida.