14.2. Estatísticas usadas pelo planejador #

14.2.1. Estatísticas mono-coluna
14.2.2. Estatísticas estendidas

14.2.1. Estatísticas mono-coluna #

Como foi visto na seção anterior, o planejador de consultas precisa estimar o número de linhas recuperadas por uma consulta para fazer boas escolhas de planos de consulta. Esta seção fornece uma visão rápida das estatísticas que o sistema usa para estas estimativas.

Um componente das estatísticas é o número total de entradas em cada tabela e índice, bem como o número de blocos de disco ocupados por cada tabela e índice. Esta informação é mantida na tabela pg_class, nas colunas reltuples e relpages. Podemos fazer a análise com consultas semelhantes a esta:

SELECT relname, relkind, reltuples, relpages
FROM pg_class
WHERE relname LIKE 'tenk1%';

       relname        | relkind | reltuples | relpages
----------------------+---------+-----------+----------
 tenk1                | r       |     10000 |      345
 tenk1_hundred        | i       |     10000 |       11
 tenk1_thous_tenthous | i       |     10000 |       30
 tenk1_unique1        | i       |     10000 |       30
 tenk1_unique2        | i       |     10000 |       30
(5 linhas)

Aqui pode-se ver que tenk1 contém 10.000 linhas, assim como seus índices, mas os índices são (sem surpresa) muito menores que a tabela.

Por motivos de eficiência, os campos reltuples e relpages não são atualizados dinamicamente, portanto, geralmente contêm valores desatualizados. Estes campos são atualizados pelos comandos VACUUM, ANALYZE, e alguns comandos DDL, como CREATE INDEX. Uma operação VACUUM ou ANALYZE que não varre toda a tabela (o que geralmente acontece) atualizará incrementalmente a contagem de reltuples com base na parte varrida da tabela, produzindo um valor aproximado. De qualquer forma, o planejador dimensionará os valores encontrados na tabela pg_class para corresponder ao tamanho da tabela física corrente, obtendo assim uma aproximação melhor.

A maioria das consultas recupera apenas uma fração das linhas de uma tabela, devido às cláusulas WHERE que restringem as linhas a serem examinadas. O planejador, portanto, precisa fazer uma estimativa da seletividade das cláusulas WHERE, ou seja, a fração das linhas que corresponde a cada condição na cláusula WHERE. As informações usadas para esta tarefa são armazenadas no catálogo do sistema pg_statistic. As entradas em pg_statistic são atualizadas pelos comandos ANALYZE e VACUUM ANALYZE, e são sempre aproximadas, mesmo quando atualizadas recentemente.

Ao invés de olhar para pg_statistic diretamente, é melhor olhar para sua visão pg_stats ao examinar as estatísticas manualmente. A visão pg_stats é projetada para ser lida mais facilmente. Além disso, pg_stats pode ser lida por todos, enquanto pg_statistic pode ser lida apenas por superusuários. (Isto evita que usuários sem privilégios aprendam algo sobre o conteúdo das tabelas de outras pessoas a partir das estatísticas. A visão pg_stats é restrita a mostrar apenas as linhas sobre tabelas que o usuário corrente pode ler.) Por exemplo, podemos executar:

SELECT attname, inherited, n_distinct,
       array_to_string(most_common_vals, E'\n') as most_common_vals
FROM pg_stats
WHERE tablename = 'road';

 attname | inherited | n_distinct |        most_common_vals
---------+-----------+------------+-------------------------------
 name    | f         | -0.5681108 | I- 580                   Ramp+
         |           |            | I- 880                   Ramp+
         |           |            | Sp Railroad                  +
         |           |            | I- 580                       +
         |           |            | I- 680                   Ramp+
         |           |            | I- 80                    Ramp+
         |           |            | 14th                     St  +
         |           |            | I- 880                       +
         |           |            | Mac Arthur               Blvd+
         |           |            | Mission                  Blvd+
...
 name    | t         |    -0.5125 | I- 580                   Ramp+
         |           |            | I- 880                   Ramp+
         |           |            | I- 580                       +
         |           |            | I- 680                   Ramp+
         |           |            | I- 80                    Ramp+
         |           |            | Sp Railroad                  +
         |           |            | I- 880                       +
         |           |            | State Hwy 13             Ramp+
         |           |            | I- 80                        +
         |           |            | State Hwy 24             Ramp+
...
 thepath | f         |          0 |
 thepath | t         |          0 |
(4 linhas)

Note serem exibidas duas linhas para a mesma coluna, uma que corresponde à hierarquia de herança completa, começando na tabela road (inherited=t), e outra incluindo apenas a própria tabela road (inherited=f). (Para maior brevidade, mostramos apenas os dez valores mais comuns para a coluna name.)

A quantidade de informações armazenadas na tabela pg_statistic pelo comando ANALYZE, em particular, o número máximo de entradas para cada coluna nas matrizes most_common_vals e histogram_bounds, pode ser definido coluna por coluna usando o comando ALTER TABLE SET STATISTICS, ou globalmente, definindo a variável de configuração default_statistics_target. No momento o limite padrão são 100 entradas. Aumentar o limite pode permitir que sejam feitas estimativas do planejador mais precisas, principalmente para colunas com distribuições de dados irregulares, ao preço de consumir mais espaço na tabela pg_statistic, e um pouco mais de tempo para calcular as estimativas. Inversamente, um limite menor pode ser suficiente para colunas com distribuições de dados simples.

Mais detalhes sobre o uso de estatísticas pelo planejador podem ser encontrados em Capítulo 69.

14.2.2. Estatísticas estendidas #

É comum observar consultas lentas executando planos de execução ruins, porque várias colunas usadas nas cláusulas da consulta estão correlacionadas. Normalmente, o planejador assume que as várias condições são independentes umas das outras, uma suposição inválida quando os valores das colunas estão correlacionados. As estatísticas regulares, devido à sua natureza de coluna individual, não podem capturar nenhum conhecimento sobre a correlação entre colunas. Entretanto, o PostgreSQL consegue calcular estatísticas multivariadas, que podem capturar estas informações.

Como o número de combinações possíveis de colunas é muito grande, é impraticável calcular estatísticas multivariadas automaticamente. Em vez disso, podem ser criados objetos de estatísticas estendidos, mais frequentemente chamados apenas de objetos de estatísticas, para instruir o servidor a obter estatísticas em conjuntos de colunas de interesse.

Os objetos de estatísticas são criados usando o comando CREATE STATISTICS. A criação deste objeto apenas cria uma entrada de catálogo expressando interesse nas estatísticas. A coleta de dados real é realizada pelo comando ANALYZE (seja pelo comando manual, ou pela autoanálise em segundo plano). Os valores coletados podem ser examinados no catálogo pg_statistic_ext_data.

O comando ANALYZE calcula estatísticas estendidas baseado na mesma amostra de linhas da tabela, necessária para calcular estatísticas regulares mono-coluna. Como o tamanho da amostra pode ser aumentado aumentando a meta de estatísticas para a tabela, ou qualquer uma de suas colunas (conforme descrito na seção anterior), uma meta de estatísticas maior normalmente produz estatísticas estendidas mais precisas, mas também exige mais tempo para calculá-las.

As subseções a seguir descrevem os tipos de estatísticas estendidas com suporte no momento.

14.2.2.1. Dependências funcionais #

O tipo mais simples de estatística estendida rastreia dependências funcionais, um conceito usado em definições de formas normais de banco de dados. Dizemos que a coluna b é funcionalmente dependente da coluna a, se o conhecimento do valor da coluna a for suficiente para determinar o valor da coluna b, ou seja, não existem duas linhas com o mesmo valor na coluna a e valores diferentes na coluna b. Em um banco de dados totalmente normalizado, as dependências funcionais devem existir apenas em chaves primárias e superchaves. Entretanto, na prática, muitos conjuntos de dados não são totalmente normalizados por vários motivos; a desnormalização intencional por motivos de desempenho é um exemplo comum. Mesmo em um banco de dados totalmente normalizado, pode haver correlação parcial entre algumas colunas, que pode ser expressa como dependência funcional parcial.

A existência de dependências funcionais afeta diretamente a precisão das estimativas em determinadas consultas. Se uma consulta contiver condições nas colunas independentes e dependentes, as condições nas colunas dependentes não reduzirão ainda mais o tamanho do resultado; mas sem conhecimento da dependência funcional, o planejador de consulta assumirá que as condições são independentes, resultando na subestimação do tamanho do resultado.

Para informar o planejador sobre dependências funcionais, o comando ANALYZE pode coletar medidas de dependência entre colunas. Avaliar o grau de dependência entre todos os conjuntos de colunas seria proibitivamente caro, então a coleta de dados é limitada aos grupos de colunas que aparecem juntas em um objeto de estatística definido com a opção dependencies (dependências). É recomendável criar estatísticas de dependências apenas para grupos de colunas fortemente correlacionados, para evitar sobrecarga desnecessária tanto no ANALYZE, quanto no planejamento de consultas posteriores.

A seguir temos um exemplo de coleta de estatísticas de dependência funcional:

CREATE STATISTICS
    stts (dependencies)
    ON city, zip
    FROM zipcodes;

ANALYZE zipcodes;

SELECT stxname, stxkeys, stxddependencies
FROM pg_statistic_ext
     join pg_statistic_ext_data on (oid = stxoid)
WHERE stxname = 'stts';

 stxname | stxkeys |             stxddependencies
---------+---------+------------------------------------------
 stts    | 1 5     | {"1 => 5": 1.000000, "5 => 1": 0.423130}
(1 linha)

Aqui pode-se ver que a coluna 1 (código postal), determina totalmente a coluna 5 (cidade), então o coeficiente é 1,0, enquanto a cidade determina o código postal somente cerca de 42% das vezes, significando existirem muitas cidades (58%) possuindo mais de um código postal.

Ao calcular a seletividade para uma consulta envolvendo colunas funcionalmente dependentes, o planejador ajusta as estimativas de seletividade por condição usando os coeficientes de dependência para não produzir uma subestimação.

14.2.2.1.1. Limitações das dependências funcionais #

No momento, as dependências funcionais são aplicadas apenas ao considerar condições de igualdade simples, que comparam colunas com valores constantes e cláusulas IN com valores constantes. As dependências funcionais não são usadas para melhorar as estimativas de condições de igualdade comparando duas colunas, ou comparando uma coluna com uma expressão, nem para cláusulas de intervalo, LIKE, ou qualquer outro tipo de condição.

Ao estimar com dependências funcionais, o planejador assume que as condições nas colunas envolvidas são compatíveis, portanto, redundantes. Se forem incompatíveis, a estimativa correta seria zero linhas, mas esta possibilidade não é considerada. Por exemplo, dada uma consulta como

SELECT *
FROM zipcodes
WHERE city = 'San Francisco'
  AND zip = '94105';

o planejador irá ignorar a cláusula city, porque não altera a seletividade, o que está correto. Entretanto, fará a mesma suposição sobre

SELECT *
FROM zipcodes
WHERE city = 'San Francisco'
  AND zip = '90210';

mesmo que de fato haja zero linhas satisfazendo esta consulta. Entretanto, as estatísticas de dependência funcional não fornecem informações suficientes para concluir isto.

Em muitas situações práticas, esta suposição é geralmente satisfeita; por exemplo, pode haver uma interface gráfica na aplicação permitindo selecionar apenas valores de cidade e CEP compatíveis para se usar em uma consulta. Mas se este não for o caso, as dependências funcionais podem não ser uma opção viável.

14.2.2.2. Contagens multivariadas N-distintas #

As estatísticas mono-coluna armazenam o número de valores distintos em cada coluna. As estimativas do número de valores distintos ao combinar mais de uma coluna (por exemplo, para GROUP BY a, b), são frequentemente erradas quando o planejador tem apenas dados estatísticos mono-coluna, fazendo com que selecione incorretamente os planos.

Para melhorar estas estimativas, o comando ANALYZE pode coletar estatísticas n-distintas para grupos de colunas. Como visto antes, é impraticável fazer isto para todos os agrupamentos de colunas possíveis, então os dados são coletados apenas para os grupos de colunas que aparecem juntos em um objeto de estatística definido com a opção ndistinct. Os dados serão coletados para cada combinação possível de duas ou mais colunas do conjunto de colunas listadas.

Continuando o exemplo anterior, as contagens n-distintas em uma tabela de ZIP Code (CEP dos EUA) podem ter a seguinte aparência:

CREATE STATISTICS
    stts2 (ndistinct)
    ON city, state, zip
    FROM zipcodes;

ANALYZE zipcodes;

SELECT stxkeys AS k, stxdndistinct AS nd
  FROM pg_statistic_ext join pg_statistic_ext_data on (oid = stxoid)
  WHERE stxname = 'stts2';
-[ RECORD 1 ]------------------------------------------------------​--
k  | 1 2 5
nd | {"1, 2": 33178, "1, 5": 33178, "2, 5": 27435, "1, 2, 5": 33178}
(1 linha)

Este resultado indica existirem três combinações de colunas possuindo 33178 valores distintos: ZIP e estado; ZIP e cidade; e ZIP, cidade e estado (o fato de serem todos iguais é esperado, porque somente o ZIP é único nesta tabela). Por outro lado, a combinação de cidade e estado possui apenas 27.435 valores distintos.

É recomendável criar objetos de estatísticas ndistinct apenas para combinações de colunas usadas de fato para agrupamento, e para as quais a estimativa incorreta do número de grupos está produzindo planos ruins. Caso contrário, o tempo para analisar e planejar será simplesmente desperdiçado.

14.2.2.3. Listas MCV multivariadas #

Outro tipo de estatística armazenada para cada coluna são as listas de valores mais comuns (most-common value, MCV). Isto permite estimativas muito precisas para colunas individuais, mas pode resultar em estimativas incorretas significativas para consultas com condições em várias colunas.

Para melhorar estas estimativas, o comando ANALYZE pode coletar listas MCV em combinações de colunas. Da mesma forma que as dependências funcionais e os coeficientes n-distintos, é impraticável fazer isto para todos os agrupamentos de colunas possíveis. Ainda mais neste caso, porque a lista MCV (ao contrário de dependências funcionais e coeficientes n-distintos) armazena os valores de coluna comuns. Portanto, os dados são coletados apenas para os grupos de colunas que aparecem juntos em um objeto de estatísticas definido com a opção mcv.

Continuando com o exemplo anterior, a lista MCV para uma tabela de códigos ZIP pode ter a seguinte aparência (ao contrário de tipos mais simples de estatísticas, é necessária uma função para inspeção do conteúdo do MCV):

CREATE STATISTICS
    stts3 (mcv)
    ON city, state
    FROM zipcodes;

ANALYZE zipcodes;

SELECT m.*
FROM pg_statistic_ext join pg_statistic_ext_data on (oid = stxoid),
     pg_mcv_list_items(stxdmcv) m WHERE stxname = 'stts3';

 index |         values         | nulls | frequency | base_frequency
-------+------------------------+-------+-----------+----------------
     0 | {Washington, DC}       | {f,f} |  0.003467 |        2.7e-05
     1 | {Apo, AE}              | {f,f} |  0.003067 |        1.9e-05
     2 | {Houston, TX}          | {f,f} |  0.002167 |       0.000133
     3 | {El Paso, TX}          | {f,f} |     0.002 |       0.000113
     4 | {New York, NY}         | {f,f} |  0.001967 |       0.000114
     5 | {Atlanta, GA}          | {f,f} |  0.001633 |        3.3e-05
     6 | {Sacramento, CA}       | {f,f} |  0.001433 |        7.8e-05
     7 | {Miami, FL}            | {f,f} |    0.0014 |          6e-05
     8 | {Dallas, TX}           | {f,f} |  0.001367 |        8.8e-05
     9 | {Chicago, IL}          | {f,f} |  0.001333 |        5.1e-05
   ...
(99 linhas)

Isto indica que a combinação mais comum de cidade e estado é Washington DC, com frequência real (na amostra) de cerca de 0,35%. A frequência base da combinação (computada a partir das frequências simples por coluna) é de apenas 0,0027%, produzindo subestimativas de duas ordens de magnitude.

É recomendável criar objetos de estatísticas MCV apenas em combinações de colunas usadas de fato juntas em condições, e para as quais a estimativa incorreta do número de grupos está produzindo planos ruins. Caso contrário, o tempo para analisar e planejar será simplesmente desperdiçado.