Índices: desempenho inteiro versus sequência se o número de nós for o mesmo

26

Estou desenvolvendo um aplicativo em Ruby on Rails com o banco de dados PostgreSQL (9.4). Para o meu caso de uso, as colunas nas tabelas serão consultadas com muita frequência, pois todo o ponto do aplicativo está procurando atributos muito específicos em um modelo.

No momento, estou decidindo se deve usar um integertipo ou simplesmente usar um tipo de string típico (por exemplo character varying(255), qual é o padrão no Rails ) para as colunas, pois não tenho certeza de qual será a diferença de desempenho no índice.

Essas colunas são enumerações . Eles têm um tamanho fixo para a quantidade de valores possíveis que podem ter. A maioria dos comprimentos de enum não excede 5, significando que o índice seria mais ou menos fixo durante toda a vida útil do aplicativo ; portanto, os índices inteiro e de seqüência de caracteres seriam idênticos no número de nós.

No entanto, a sequência que seria indexada poderia ter cerca de 20 caracteres, que na memória é aproximadamente 5x do número inteiro (se um número inteiro tiver 4 bytes e as seqüências de caracteres forem ASCII puro a 1 byte por caractere, isso é válido). Não sei como os mecanismos de banco de dados fazem pesquisas de índice, mas se precisar "varrer" a string até corresponder exatamente , isso significa que a pesquisa de string seria 5x mais lenta que uma pesquisa inteira; a "varredura" até a correspondência para a pesquisa inteira seria de 4 bytes em vez de 20. Isso é o que estou imaginando:

O valor da pesquisa é (inteiro) 4:

digitalizando ............................ ENCONTRADO | obtendo registros ... | BYTE_1 | BYTE_2 | BYTE_3 | BYTE_4 | BYTE_5 | BYTE_6 | BYTE_7 | BYTE_8 | ... |

O valor da pesquisa é (string) "some_val" (8 bytes):

digitalizando ................................................. .................................... ENCONTRADO | obtendo registros ... | BYTE_1 | BYTE_2 | BYTE_3 | BYTE_4 | BYTE_5 | BYTE_6 | BYTE_7 | BYTE_8 | ... |

Espero que isso faça sentido. Basicamente, como o número inteiro ocupa menos espaço, ele pode ser "correspondido" mais rapidamente do que o equivalente em cadeia. Talvez esse seja um palpite completamente errado, mas eu não sou especialista, por isso estou perguntando a vocês! Suponho que essa resposta que acabei de encontrar parece apoiar minha hipótese, mas quero ter certeza.

O número de valores possíveis na coluna não mudaria no uso de nenhum deles, portanto o próprio índice não mudaria (a menos que eu adicionasse um novo valor à enumeração). Nesse caso, haveria uma diferença de desempenho no uso de integerou varchar(255), ou o uso de um tipo inteiro faz mais sentido?


A razão pela qual estou perguntando é que o enumtipo do Rails mapeia números inteiros para chaves de string, mas eles não devem ser colunas voltadas para o usuário. Essencialmente, você não pode verificar se o valor da enumeração é válido, porque um valor inválido causará um ArgumentErrorantes que qualquer validação possa ser executada. O uso de um stringtipo permitiria validações, mas se houver um custo de desempenho, eu preferiria me afastar do problema de validação.

Chris Cirefice
fonte

Respostas:

32

Resposta curta: integeré mais rápido do que varcharou textem todos os aspectos. Não importa muito para pequenas mesas e / ou teclas curtas. A diferença aumenta com o comprimento das chaves e o número de linhas.

string ... 20 caracteres, que na memória são aproximadamente 5x do número inteiro (se um número inteiro tiver 4 bytes e as strings forem ASCII puro com 1 byte por caractere, isso é válido)

Para ser mais preciso, os tipos de caracteres ( textou varchar) ocupam exatamente 21 bytes para 20 caracteres ASCII no disco e 23 bytes na RAM. Avaliação detalhada:

Também importante: as COLLATIONregras podem tornar a classificação dos dados dos caracteres mais dispendiosa - ao contrário dos tipos de dados numéricos:

O tamanho do índice é provavelmente responsável pela maior parte da diferença de desempenho na maioria dos casos. Considere a sobrecarga por tupla de índice (basicamente a mesma de uma tabela): 4 bytes para o ponteiro do item e 24 bytes para o cabeçalho da tupla. Portanto, a tupla de índice integerequivaleria a 36 bytes (incluindo 4 bytes de preenchimento de alinhamento ) e, para varchar(20)20 caracteres ASCII, seriam 52 bytes (também incluindo preenchimento). Detalhes:

Toda a teoria de lado: é melhor apenas testar:

O Postgres 9.5 introduziu uma otimização para classificar longas sequências de dados de caracteres (palavra-chave "chaves abreviadas" ). Mas um bug em algumas funções da biblioteca C no Linux forçou o projeto a desativar o recurso para agrupamentos não-C no Postgres 9.5.2. Detalhes nas notas de versão.

No entanto, se você realmente usa os enumtipos do Postgres , a maioria dessas considerações é irrelevante, pois elas são implementadas com integervalores internamente de qualquer maneira. O manual:

Um enumvalor ocupa quatro bytes no disco.

Além: varchar(255)usado para fazer sentido para versões anteriores do SQL Server, que poderiam usar um tipo de dados mais eficiente internamente até o limite de 255 caracteres. Mas a restrição de comprimento ímpar de 255 caracteres não tem nenhum impacto especial no desempenho do Postgres.

Erwin Brandstetter
fonte
11
Não há otimização escondido em SQL Server para varchar(255)vs. por exemplo varchar(260). Pode ter acontecido isso com o SQL Server 6.x, mas isso não ocorre há muito tempo.
A_horse_with_no_name
@a_horse_with_no_name: obrigado, esclarei em conformidade.
Erwin Brandstetter
Desculpe por ter demorado tanto para aceitar isso, eu tenho sido lenta no desenvolvimento desse projeto;)
Chris Cirefice
Esta resposta ainda é válida para o Postgres 10, por favor?
Matty
11
@ Matty: Ainda válido. E também não vejo nada mudando na página 11.
Erwin Brandstetter