¿Qué se recupera del disco durante una consulta?

13

Pregunta bastante simple, probablemente respondida en alguna parte, pero parece que no puedo formar la pregunta de búsqueda correcta para Google ...

¿El número de columnas en una tabla en particular afecta el rendimiento de una consulta, cuando se consulta en un subconjunto de esa tabla?

Por ejemplo, si la tabla Foo tiene 20 columnas, pero mi consulta solo selecciona 5 de esas columnas, ¿tener 20 (en lugar de, por ejemplo, 10) columnas afecta el rendimiento de la consulta? Suponga por simplicidad que cualquier cosa en la cláusula WHERE está incluida en esas 5 columnas.

Me preocupa el uso de la memoria caché del búfer de Postgres además de la memoria caché del disco del sistema operativo. Tengo muy poca comprensión del diseño de almacenamiento físico de Postgres. Las tablas se almacenan en varias páginas (el tamaño predeterminado es de 8k por página), pero no entiendo cómo se organizan las tuplas desde allí. ¿PG es lo suficientemente inteligente como para obtener solo del disco los datos que comprenden esas 5 columnas?

Jmoney38
fuente
Estás hablando de buscar 50 bytes pero no los 150 restantes. ¡Tu disco probablemente lea en incrementos mayores que eso!
Andomar
¿De dónde sacas esos números?
Jmoney38

Respuestas:

14

El almacenamiento físico para las filas se describe en los documentos en Diseño de página de base de datos . El contenido de la columna para la misma fila se almacena en la misma página del disco, con la notable excepción de los contenidos editados de TOAST (demasiado grandes para caber en una página). El contenido se extrae secuencialmente dentro de cada fila, como se explica:

Para leer los datos, debe examinar cada atributo por turno. Primero verifique si el campo es NULL de acuerdo con el mapa de bits nulo. Si es así, pasa a la siguiente. Luego, asegúrese de tener la alineación correcta. Si el campo es un campo de ancho fijo, entonces todos los bytes simplemente se colocan.

En el caso más simple (sin columnas TOAST), postgres buscará toda la fila, incluso si se necesitan pocas columnas. Entonces, en este caso, la respuesta es sí, tener más columnas puede tener un claro impacto adverso en la memoria caché del búfer de desperdicio, particularmente si el contenido de la columna es grande mientras aún está por debajo del umbral de TOAST.

Ahora el caso de TOAST: cuando un campo individual excede ~ 2kB, el motor almacena el contenido del campo en una tabla física separada. También entra en juego cuando la fila completa no cabe en una página (8kB por defecto): algunos de los campos se mueven al almacenamiento de TOAST. Doc dice:

Si es un campo de longitud variable (attlen = -1), entonces es un poco más complicado. Todos los tipos de datos de longitud variable comparten la estructura de encabezado común struct varlena, que incluye la longitud total del valor almacenado y algunos bits de marca. Dependiendo de las banderas, los datos pueden estar en línea o en una tabla TOAST; también podría estar comprimido

Los contenidos de TOAST no se obtienen cuando no se necesitan explícitamente, por lo que su efecto en el número total de páginas que se debe recuperar es pequeño (unos pocos bytes por columna). Esto explica los resultados en la respuesta de @ dezso.

En cuanto a las escrituras, cada fila con todas sus columnas se reescribe por completo en cada ACTUALIZACIÓN, sin importar qué columnas se cambien. Entonces, tener más columnas es obviamente más costoso para las escrituras.

Daniel Vérité
fuente
Esa es una respuesta increíble. Exactamente lo que estoy buscando. Gracias.
Jmoney38
1
Un buen recurso que encontré en lo que respecta a la estructura de filas (página inspeccionada y algunos ejemplos de uso) aquí .
Jmoney38
9

La respuesta de Daniel se centra en el costo de leer filas individuales. En este contexto: poner NOT NULLcolumnas de tamaño fijo primero en su tabla ayuda un poco. Poner las columnas relevantes primero (las que buscas) ayuda un poco. Minimizar el relleno (debido a la alineación de datos) al jugar tetris de alineación con sus columnas puede ayudar un poco. Pero el efecto más importante aún no se ha mencionado, especialmente para tablas grandes.

Obviamente, las columnas adicionales hacen que una fila cubra más espacio en el disco, por lo que caben menos filas en una página de datos (8 kB por defecto). Las filas individuales se extienden por más páginas. El motor de la base de datos generalmente tiene que buscar páginas enteras, no filas individuales . Poco importa si las filas individuales son algo más pequeñas o más grandes, siempre y cuando haya que leer el mismo número de páginas.

Si una consulta obtiene una porción (relativamente) pequeña de una tabla grande, donde las filas se extienden más o menos al azar en toda la tabla, con el apoyo de un índice, esto dará como resultado aproximadamente el mismo número de lecturas de página, con poca consideración a tamaño de fila. Las columnas irrelevantes no lo retrasarán mucho en un caso tan raro.

Normalmente, buscará parches o grupos de filas que se hayan ingresado en secuencia o proximidad y compartirá páginas de datos. Esas filas se extienden debido al desorden, se deben leer más páginas de disco para satisfacer su consulta. Tener que leer más páginas suele ser la razón más importante para que una consulta sea más lenta. Y ese es el factor más importante por el cual las columnas irrelevantes hacen que sus consultas sean más lentas.

Con grandes bases de datos, normalmente no hay suficiente RAM para mantener todo en la memoria caché. Las filas más grandes ocupan más caché, más contención, menos aciertos de caché, más E / S de disco. Y las lecturas de disco suelen ser mucho más caras. Menos con los SSD, pero sigue habiendo una diferencia sustancial. Esto se suma al punto anterior sobre las lecturas de página.

Se puede o no importa si las columnas son irrelevantes TOSTADA-ed. Las columnas relevantes también pueden ser TOAST-ed, devolviendo el mismo efecto.

Erwin Brandstetter
fuente
1

Una pequeña prueba:

CREATE TABLE test2 (
    id serial PRIMARY KEY,
    num integer,
    short_text varchar(32),
    longer_text varchar(1000),
    long_long_text text
);

INSERT INTO test2 (num, short_text, longer_text, long_long_text)
SELECT i, lpad('', 32, 'abcdefeghji'), lpad('', 1000, 'abcdefeghji'), lpad('', (random() * 10000)::integer, 'abcdefeghji')
FROM generate_series(1, 10000) a(i);

ANALYZE test2;

SELECT * FROM test2;
[...]
Time: 1091.331 ms

SELECT num FROM test2;
[...]
Time: 21.310 ms

Limitar la consulta a las primeras 250 filas ( WHERE num <= 250) da como resultado 34.539 ms y 8.343 ms, respectivamente. Seleccionar todos menos long_long_textde este conjunto limitado da como resultado 18.432 ms. Esto muestra que, en sus términos, PG es lo suficientemente inteligente.

dezso
fuente
Bueno, ciertamente aprecio el aporte. Sin embargo, no puedo decir con certeza que este escenario de prueba demuestra lo que propuse originalmente. Hay algunos problemas. Por un lado, cuando ejecuta "SELECT * FROM test2" por primera vez, eso debería haber llenado su caché de búfer compartido. Esa consulta habría tardado mucho más en recuperarse del disco. Por lo tanto, la segunda consulta teóricamente habría sido mucho más rápida porque habría estado obteniendo de la caché SB. Pero estoy de acuerdo en que 'sugiere' que PG solo obtiene las filas que necesita, según sus pruebas / comparaciones posteriores.
Jmoney38
Tienes razón, esta prueba (siendo simple) tiene sus defectos. Si tengo suficiente tiempo, trataré de cubrirlos también.
dezso