O que é recuperado do disco durante uma consulta?

13

Pergunta bastante simples, provavelmente respondida em algum lugar, mas não consigo formar a pergunta de pesquisa correta para o Google ...

O número de colunas em uma tabela específica afeta o desempenho de uma consulta ao consultar em um subconjunto dessa tabela?

Por exemplo, se a tabela Foo possui 20 colunas, mas minha consulta seleciona apenas 5 dessas colunas, ter 20 (versus, digamos, 10) colunas afeta o desempenho da consulta? Suponha, por simplicidade, que qualquer coisa na cláusula WHERE esteja incluída nessas 5 colunas.

Estou preocupado com o uso do cache de buffer do Postgres, além do cache de disco do sistema operacional. Perdi muito a noção do design de armazenamento físico do Postgres. As tabelas são armazenadas em várias páginas (com tamanho padrão de 8k por página), mas não entendo como as tuplas são organizadas a partir daí. O PG é inteligente o suficiente para buscar apenas do disco os dados que compõem essas 5 colunas?

Jmoney38
fonte
Você está falando em buscar 50 bytes, mas não os 150 restantes. Seu disco provavelmente lê em incrementos maiores que isso!
Andomar 5/06
De onde você está tirando esses números?
Jmoney38

Respostas:

14

O armazenamento físico para linhas é descrito nos documentos em Layout de página do banco de dados . O conteúdo da coluna para a mesma linha é todo armazenado na mesma página do disco, com a exceção notável do TOAST conteúdo (muito grande para caber em uma página). O conteúdo é extraído sequencialmente dentro de cada linha, conforme explicado:

Para ler os dados, você precisa examinar cada atributo por vez. Primeiro verifique se o campo é NULL de acordo com o bitmap nulo. Se for, vá para o próximo. Em seguida, verifique se você tem o alinhamento correto. Se o campo for de largura fixa, todos os bytes serão simplesmente colocados.

No caso mais simples (sem colunas TOAST), o postgres buscará a linha inteira, mesmo que sejam necessárias poucas colunas. Portanto, nesse caso, a resposta é sim, ter mais colunas pode ter um claro impacto adverso no cache do buffer do waster, principalmente se o conteúdo da coluna for grande enquanto ainda estiver abaixo do limite do TOAST.

Agora, o caso TOAST: quando um campo individual excede ~ 2kB, o mecanismo armazena o conteúdo do campo em uma tabela física separada. Também entra em ação quando a linha inteira não se encaixa em uma página (8kB por padrão): alguns dos campos são movidos para o armazenamento TOAST. Doc diz:

Se é um campo de tamanho variável (attlen = -1), é um pouco mais complicado. Todos os tipos de dados de comprimento variável compartilham a estrutura de cabeçalho comum struct varlena, que inclui o comprimento total do valor armazenado e alguns bits de flag. Dependendo dos sinalizadores, os dados podem estar em linha ou em uma tabela TOAST; também pode ser compactado

O conteúdo do TOAST não é buscado quando não é explicitamente necessário; portanto, seu efeito no número total de páginas a serem buscadas é pequeno (alguns bytes por coluna). Isso explica os resultados na resposta do @ dezso.

Quanto às gravações, cada linha com todas as suas colunas é totalmente reescrita em cada UPDATE, independentemente de quais colunas sejam alteradas. Portanto, ter mais colunas é obviamente mais caro para gravações.

Daniel Vérité
fonte
Essa é uma resposta rápida. Exatamente o que estou procurando. Obrigado.
Jmoney38
1
Um bom recurso que encontrei em relação à estrutura de linhas (inspeção de página e alguns exemplos de uso) aqui .
precisa
9

A resposta de Daniel se concentra no custo da leitura de linhas individuais. Nesse contexto: Colocar NOT NULLcolunas de tamanho fixo em primeiro lugar em sua tabela ajuda um pouco. Colocar as colunas relevantes em primeiro lugar (as que você consulta) ajuda um pouco. Minimização do preenchimento (devido ao alinhamento de dados) tocando o tetris de alinhamento com suas colunas pode ajudar um pouco. Mas o efeito mais importante ainda não foi mencionado, especialmente para grandes mesas.

Colunas adicionais obviamente fazem com que uma linha cubra mais espaço em disco, para que menos linhas caibam em uma página de dados (8 kB por padrão). Linhas individuais estão espalhadas por mais páginas. O mecanismo de banco de dados geralmente precisa buscar páginas inteiras, não linhas individuais . Pouco importa se as linhas individuais são um pouco menores ou maiores - desde que o mesmo número de páginas precise ser lido.

Se uma consulta buscar uma parte (relativamente) pequena de uma tabela grande, onde as linhas estão espalhadas mais ou menos aleatoriamente por toda a tabela, suportada por um índice, isso resultará em aproximadamente o mesmo número de leituras de página, com pouca consideração para tamanho de linha. Colunas irrelevantes não o atrasarão muito nesse caso (raro).

Normalmente, você busca patches ou agrupamentos de linhas que foram inseridos em sequência ou proximidade e compartilha páginas de dados. Essas linhas são espalhadas devido à confusão, mais páginas do disco precisam ser lidas para satisfazer sua consulta. Ter que ler mais páginas geralmente é o motivo mais importante para uma consulta ser mais lenta. E esse é o fator mais importante por que colunas irrelevantes tornam suas consultas mais lentas.

Com grandes bancos de dados, normalmente não há RAM suficiente para manter tudo na memória cache. Linhas maiores ocupam mais cache, mais contenção, menos ocorrências de cache, mais E / S de disco. E as leituras de disco geralmente são muito mais caras. Menos com os SSDs, mas uma diferença substancial permanece. Isso adiciona ao ponto acima sobre leituras de página.

Ele pode ou não importa se colunas irrelevantes são BRINDE-ed. As colunas relevantes também podem ser editadas pelo TOAST, trazendo de volta praticamente o mesmo efeito.

Erwin Brandstetter
fonte
1

Um pequeno teste:

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 a consulta às primeiras 250 linhas ( WHERE num <= 250) resulta em 34.539 ms e 8.343 ms, respectivamente. A seleção de todos, exceto long_long_textdeste conjunto limitado, resulta em 18,432 ms. Isso mostra que, nos seus termos, o PG é inteligente o suficiente.

dezso
fonte
Bem, certamente aprecio a entrada. No entanto, não posso dizer com certeza que esse cenário de teste prova o que propus originalmente. Existem alguns problemas. Por um lado, quando você executa "SELECT * FROM test2" pela primeira vez, isso deve ter preenchido seu cache de buffer compartilhado. Essa consulta levaria muito mais tempo para recuperar do disco. Assim, a 2ª consulta teoricamente teria sido muito mais rápida, porque teria buscado no cache do SB. Mas eu concordo que ele 'sugere' que o PG busque apenas as linhas necessárias, com base em seus testes / comparações posteriores.
Jmoney38
Você está certo, este teste (sendo simples) tem suas falhas. Se eu tiver tempo suficiente, tentarei cobrir isso também.
Dez