Índices: rendimiento de enteros frente a cadenas si el número de nodos es el mismo

26

Estoy desarrollando una aplicación en Ruby on Rails con la base de datos PostgreSQL (9.4). Para mi caso de uso, las columnas en las tablas se buscarán con mucha frecuencia, ya que todo el punto de la aplicación busca atributos muy específicos en un modelo.

Actualmente estoy decidiendo si usar un integertipo o simplemente usar un tipo de cadena típico (por ejemplo character varying(255), que es el predeterminado en Rails ) para las columnas, ya que no estoy seguro de cuál será la diferencia de rendimiento en el índice.

Estas columnas son enumeraciones . Tienen un tamaño fijo para la cantidad de valores posibles que pueden tener. La mayoría de las longitudes de enumeración no exceden de 5, lo que significa que el índice estaría más o menos fijo durante la vida útil de la aplicación ; por lo tanto, los índices de enteros y cadenas serían idénticos en el número de nodos.

Sin embargo, la cadena que se indexaría podría tener alrededor de 20 caracteres, que en la memoria es aproximadamente 5 veces mayor que la del entero (si un entero es de 4 bytes, y las cadenas son ASCII puro a 1 byte por carácter, entonces esto se mantiene). No sé cómo los motores de base de datos realizan búsquedas de índice, pero si necesita "escanear" la cadena hasta que coincida exactamente , entonces, en esencia, eso significa que la búsqueda de cadena sería 5 veces más lenta que una búsqueda de enteros; el "escaneo" hasta la coincidencia para la búsqueda de enteros sería de 4 bytes en lugar de 20. Esto es lo que estoy imaginando:

El valor de búsqueda es (entero) 4:

escaneo ............................ ENCONTRADO | obtener registros ... | BYTE_1 | BYTE_2 | BYTE_3 | BYTE_4 | BYTE_5 | BYTE_6 | BYTE_7 | BYTE_8 | ... |

El valor de búsqueda es (cadena) "some_val" (8 bytes):

exploración................................................. .................................... ENCONTRADO | obtener registros ... | BYTE_1 | BYTE_2 | BYTE_3 | BYTE_4 | BYTE_5 | BYTE_6 | BYTE_7 | BYTE_8 | ... |

Espero que eso tenga sentido. Básicamente, debido a que el entero ocupa menos espacio, puede "emparejarse" más rápido que su contraparte de cadena. Tal vez esta es una suposición completamente equivocada, pero no soy un experto, ¡por eso les pregunto! Supongo que esta respuesta que acabo de encontrar parece apoyar mi hipótesis, pero quiero estar seguro.

El número de valores posibles en la columna no cambiaría al usar ninguno de los dos, por lo que el índice en sí no cambiaría (a menos que agregue un nuevo valor a la enumeración). En este caso, ¿habría alguna diferencia de rendimiento al usar integero varchar(255), o tiene más sentido usar un tipo entero?


La razón por la que pregunto es que el enumtipo de Rails asigna enteros a las teclas de cadena, pero no están destinados a ser columnas orientadas al usuario. Básicamente, no puede verificar que el valor de enumeración sea válido, porque un valor no válido generará un ArgumentErrorantes de que se puedan ejecutar las validaciones. El uso de un stringtipo permitiría validaciones, pero si hay un costo de rendimiento, prefiero simplemente solucionar el problema de validación.

Chris Cirefice
fuente

Respuestas:

32

Respuesta corta: integeres más rápido que varcharo texten todos los aspectos. No importará mucho para mesas pequeñas y / o teclas cortas. La diferencia crece con la longitud de las teclas y el número de filas.

cadena ... 20 caracteres de longitud, que en la memoria es aproximadamente 5 veces la del entero (si un entero es de 4 bytes, y las cadenas son ASCII puro a 1 byte por carácter, entonces esto se mantiene)

Para ser precisos, los tipos de caracteres ( texto varchar) ocupan exactamente 21 bytes para 20 caracteres ASCII en el disco y 23 bytes en la RAM. Evaluación detallada:

También es importante: las COLLATIONreglas pueden hacer que la clasificación de datos de caracteres sea más costosa, a diferencia de los tipos de datos numéricos:

El tamaño del índice es probablemente responsable de la mayor parte de la diferencia de rendimiento en la mayoría de los casos. Considere la sobrecarga por tupla de índice (básicamente la misma que para una tabla): 4 bytes para el puntero del elemento y 24 bytes para el encabezado de la tupla. Por lo tanto, la tupla de índice para integerascendería a 36 bytes (incluidos 4 bytes de relleno de alineación ) y para varchar(20)20 caracteres ASCII sería de 52 bytes (también incluye relleno). Detalles:

Toda la teoría a un lado: es mejor simplemente probar:

Postgres 9.5 introdujo una optimización para ordenar cadenas largas de datos de caracteres (palabra clave "claves abreviadas" ). Pero un error en algunas funciones de la biblioteca C en Linux obligó al proyecto a deshabilitar la función para intercalaciones que no son C en Postgres 9.5.2. Detalles en las notas de la versión.

Sin embargo, si realmente utiliza los enumtipos de Postgres , la mayoría de estas consideraciones son irrelevantes, ya que de integertodos modos se implementan con valores internos. El manual:

Un enumvalor ocupa cuatro bytes en el disco.

Aparte: varchar(255)solía tener sentido para las primeras versiones de SQL Server, que podrían usar un tipo de datos más eficiente internamente hasta el límite de 255 caracteres. Pero la restricción de longitud impar de 255 caracteres no tiene ningún impacto especial en el rendimiento en Postgres.

Erwin Brandstetter
fuente
1
No hay optimización oculta en SQL Server para varchar(255)vs. ej varchar(260). Es posible que haya habido algo así con SQL Server 6.x, pero esto no ha sido así durante mucho tiempo.
a_horse_with_no_name
@a_horse_with_no_name: gracias, lo aclaré en consecuencia.
Erwin Brandstetter
Perdón por tomar tanto tiempo en aceptar esto, he tardado en el desarrollo de ese proyecto;)
Chris Cirefice
¿Esta respuesta sigue siendo válida para Postgres 10, por favor?
Matty
1
@Matty: sigue siendo válido. Y tampoco veo nada que cambie para la página 11 todavía.
Erwin Brandstetter