Eu tenho uma tabela com milhões de linhas e quero encontrar todas as linhas que contenham qualquer um de uma lista fornecida de alguns milhares de valores em uma coluna específica. Basicamente, quero executar uma IN(...set...)
consulta, que é reescrita internamente em uma = ANY(...array...)
construção, com um tamanho de matriz de milhares, em uma coluna indexada com milhões de linhas.
Minhas perguntas são:
- Existe um limite para o tamanho de um conjunto ou array neste tipo de consulta?
- Como esse tipo de consulta é dimensionado? Presumo que a matriz não esteja indexada, então, presumivelmente, cada valor da matriz atinge o índice, fornecendo escala
O(n log N)
paran
valores da matriz eN
linhas da tabela. - Qual seria o impacto no rendimento da consulta ao enviar esses tipos de consultas grandes, em meio a um fluxo de consultas mais simples? Em outras palavras, seria bom dividir isso em algumas dezenas de consultas separadas, com, digamos, 100 valores de array em cada uma, para permitir que o trabalho desta consulta fosse intercalado com outras consultas?
O apêndice de limites informará que o número máximo de parâmetros de consulta é 65.535 e, se não houver memória, o limite para uma mensagem (consulta) é de meio GB.
Naturalmente, o desempenho irá piorar gradualmente à medida que a lista aumenta. Eu recomendaria enviar um único parâmetro de array em vez de milhares de valores individuais. Uma abordagem alternativa é colocar
COPY
os valores em uma tabela temporária e juntá-los a ela. Para tamanhos de lista normais, não vi nenhuma vantagem nisso, mas evita os limites e pode ser benéfico para listas enormes.No final, você mesmo terá que avaliar isso. Se você precisar de listas enormes como essa, reavalie suas escolhas de design.
Fiz um pequeno benchmark.
Código fonte no pastebin
Tabela de teste: 10 milhões de linhas com (id INT PRIMARY KEY, s TEXT).
Resultados:
Interpretação:
Não há diferença entre "WHERE id IN (...)" e "WHERE id =ANY(...)".
Supondo que a coluna pesquisada esteja indexada, ele faz uma pesquisa de índice para cada valor do array, a um custo de O(log N). Com n valores de matriz, esse é um custo total de O (n log N). Como esperado, há um pequeno custo fixo para executar uma consulta e, em seguida, ela é dimensionada de forma praticamente linear com o número de linhas retornadas.
Incluí dois casos: "Correlacionado", onde os IDs das linhas recuperadas são consecutivos, e "Aleatório", onde são randomizados em toda a tabela. Como esperado, os vários caches (da CPU L1 ao cache de disco do sistema operacional) fazem seu trabalho, por isso é mais rápido recuperar dados com maior localidade de referência.
De qualquer forma, a 2 microssegundos por linha, a carga da CPU do banco de dados é bastante baixa.
No entanto, isso é executado em um SSD e a tabela é armazenada em cache na RAM. Em uma situação mais "real", onde partes da tabela não seriam armazenadas em cache, se você recuperar linhas aleatórias, poderá esperar um acesso aleatório por linha. Isso pode ser bem lento, dependendo do seu hardware, mas... isso não tem nada a ver com o próprio postgres. Isso depende inteiramente do seu sistema IO e de quão bem seus dados são armazenados em cache. Se você usa discos giratórios e os dados não estão armazenados em cache, e você não se importa particularmente com o fato de essa consulta ser o mais rápida possível, talvez dividi-la em listas menores de linhas reduza o lixo do disco.
Também incluí um terceiro caso de teste:
Quando o comprimento do array é muito grande, as outras consultas simplesmente fazem uma varredura sequencial paralela. Isso é muito rápido, porque o "Filtro onde id=ANY(...)" não é burro, ele usa algum tipo de pesquisa rápida como hashing ou bisect, não compara cada linha com cada valor do array.
Esta última consulta é interessante porque é uma junção, então o postgres a otimiza como uma junção, que pode ser mais rápida... ou mais lenta... em alguns casos.