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.
É 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.
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.
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.
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.
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.