Ir para o conteúdo
ou

Software livre Brasil

 Voltar a SAVEPOINT
Tela cheia

Arrumando a ordem das colunas em índices compostos

27 de Junho de 2011, 0:00 , por Software Livre Brasil - 0sem comentários ainda | Ninguém está seguindo este artigo ainda.
Visualizado 1305 vezes

O título é grande mas eu vou tentar explicar da forma mais rápida possível. Na verdade o caso não é tão simples assim…. vejamos:

O problema

  • É comum você precisar criar índices com várias colunas ao mesmo tempo. Em chaves naturais isso ocorre com frequência e é quase obrigatório em relações de “N para N”, quando uma tabela intermediária recebe como chave primária (vou abreviar aqui como PK, ou Primary Key) as chaves de duas tabelas, como a famosa tabela “detalhes_pedido” com as chaves da tabela “pedido” e “produto”.
  • A ordem dos tratores altera completamente o viaduto. Se você tem as colunas A, B e C num índice composto, você tem de criar o índice com numa ordem específica: da coluna com menor variação de dados, para a coluna com maior variação de dados.
  • Vamos entender que variação aqui, é a quantidade de valores diferentes que uma coluna pode assumir em relação ao número de registros da tabela. Uma coluna que sozinha responde pela PK tem 100% de variação, pois todos os registros tem valores distintos. Uma tabela que possui apenas 2 valores distintos numa tabela com milhares de registros, tem 50% de variação.
  • No catálogo do PostgreSQL (o PG_CATALOG), temos 2 lugares que mostram a variação de uma coluna: pg_statistics e a visão sobre esta tabela que é a pg_stats . Estas tabelas são alimentadas pela rotina de VACUUM ANALYZE.
  • Índices criados com uma ordem errada, onde colunas com variação baixa são colocadas no final do comando CREATE INDEX ou na definição de uma PK ou UK (que criam índices implícitos com na mesma ordem da restrição PK ou UK) vão resultar num índice ineficiente. Esta ineficiência se traduz em desperdício de espaço em disco, maior sentidão no processo de INDEX SCAN e há terríveis casos onde uma consulta com um filtro (leia-se WHERE) num dos campos do índice não vai conseguir utilizar aquele índice.
  • Este problema não é uma particularidade do PostgreSQL, ele ocorre nos demais SGDBs também. Infelizmente a maneira de detectar o problema é diferente em cada um, pois o acesso à este tipo de informação não está presente no Information Schema, que faz parte do padrão SQL;
  • Na tabela pg_statistic a coluna ‘stadistinct’ que equivale à coluna n_distinct na visão pg_stats. O significado da coluna não é tão trivial. Vejamos:
    • Se o valor for 0 (zero), então significa que não foi coletada nenhuma informação no ANALYZE. Rode esta rotina e depois verifique novamente;
    • Se o valor for positivo, então ele indica o número de valores diferentes que a coluna recebe. O valor positivo também significa que ao rodar o ANALYZE, o PostgreSQL acredita (ele pode errar) que o número de valores é fixo, ou seja, são sempre os mesmos valores nas colunas, independente do número de registros.
    • Se o valor for negativo, então você tem o número de valores distintos naquela coluna já dividido pelo número de registros da tabela.

Verificando as estatísticas de um único índice

Bom, então eu criei uma consulta para verificar como estão as estatísticas de cada coluna de um índice específico:

SELECT 
    n.nspname    AS "Schema", 
    c2.relname   AS "Table", 
    c.relname    AS "Index",
    TO_CHAR(c.reltuples,'999G999G999') AS num_rows,
    c.relnatts   AS col_count, 
    a.attnum     AS Ord,
    a.attname    AS "Column",
    TO_CHAR(CASE WHEN s.n_distinct = 0 THEN 0
             WHEN s.n_distinct < 0 THEN @ 100 * s.n_distinct
             WHEN s.n_distinct > 0 THEN 100 * s.n_distinct / c2.reltuples
             END ,'000D99')      AS "%Var",
    format_type(a.atttypid, a.atttypmod) AS "Type"
FROM
        pg_class          AS c
        JOIN pg_namespace AS n ON n.oid = c.relnamespace
        JOIN pg_index     AS i ON c.oid = i.indexrelid
        JOIN pg_class     AS c2 ON c2.oid = indrelid
        JOIN pg_attribute AS a ON a.attrelid = i.indexrelid
        JOIN pg_stats AS s ON 
            s.tablename = c2.relname AND s.attname = a.attname
    WHERE
        n.nspname NOT IN ('pg_catalog','information_schema') AND
        a.attnum > 0 AND 
        NOT a.attisdropped  AND
        c.relnatts > 0 AND
        c.relname = 'nome_do_índice'
ORDER BY a.attnum;

Vejamos como esta consulta fica com o índice film_actor_pkey da base PAGILA:

Schema |   Table    |      Index      |   num_rows   | col_count | ord |  Column  |  %Var   |   Type
-------+------------+-----------------+--------------+-----------+-----+----------+---------+----------
public | film_actor | film_actor_pkey |        5.462 |         2 |   1 | actor_id |  003,66 | smallint
public | film_actor | film_actor_pkey |        5.462 |         2 |   2 | film_id  |  018,25 | smallint

Aqui notamos que o índice está OK, pois a 1ª coluna tem ~3,7% de variação e a 2ª tem ~18%.

Verificando todos índices com ordem trocada na base

O problema é que eu queria verificar todos os índices (tirando os índices do próprio catálogo) que possuem índices onde a ordem está invertida. Então eu montei a consulta abaixo para me ajudar:

WITH multi_col AS (SELECT 
    i.indexrelid AS index_oid,
    n.nspname    AS schema,
    c2.relname   AS TABLE,
    c2.reltuples AS row_num,
    c.relname    AS INDEX,
    a.attnum     AS n_col,
    a.attname    AS col,
    CASE WHEN s.n_distinct = 0 THEN 0
        WHEN s.n_distinct < 0 THEN @ s.n_distinct
        WHEN s.n_distinct > 0 THEN s.n_distinct / c2.reltuples
         END AS var
  FROM 
    pg_class AS c
    JOIN pg_namespace AS n  ON n.oid = c.relnamespace
    JOIN pg_index     AS i  ON c.oid = i.indexrelid
    JOIN pg_class     AS c2 ON c2.oid = indrelid
    JOIN pg_attribute AS a  ON a.attrelid = i.indexrelid
    JOIN pg_stats     AS s  ON s.tablename = c2.relname AND s.attname = a.attname
  WHERE
    n.nspname NOT IN ('pg_catalog','information_schema') AND
    a.attnum > 0 AND 
    NOT a.attisdropped  AND
    c.relnatts > 1
), wrong_col AS (SELECT 
    col_a.index_oid,
    col_a.col
  FROM
    multi_col AS col_a
    JOIN multi_col AS col_b ON
    col_a.index_oid = col_b.index_oid AND
    col_a.n_col = col_b.n_col + 1 AND
    col_a.var < col_b.var)
SELECT DISTINCT
    col.schema,
    col.TABLE,
    col.INDEX,
    TO_CHAR(col.row_num,'999G999G999'),
    col.n_col,
    col.col || CASE wrong_ind_col.col WHEN col.col THEN ' ***' ELSE '' END AS col,
    TO_CHAR(col.var,'0D99999') AS var,
    pg_get_indexdef(col.index_oid) AS "CREATE"
  FROM
    multi_col AS col
    JOIN wrong_col AS wrong_index ON col.index_oid = wrong_index.index_oid
    LEFT JOIN wrong_col AS wrong_ind_col ON 
        col.index_oid = wrong_ind_col.index_oid AND 
        col.col = wrong_ind_col.col
  WHERE row_num > 1000
  ORDER BY
    col.schema,
    col.TABLE,
    col.INDEX, 
    col.n_col;

Observações

  • Esta consulta utiliza um pouco de CTE para facilitar, portanto só funciona a partir do PostgreSQL 8.4.
  • Cuidado!!! Estas consultas não fazem o menor sentido se o ANALYZE não estiver atualizado e se você não está utilizando dados reais na sua base. Se você tem uma base de testes com dados fictícios, então você tem de confiar na sua capacidade de modelagem quando você prevê qual coluna tem variação maior.
  • Eu coloquei no meio desta consulta um filtro para pegar apenas os índices de tabelas com mais de mil registros. Índices de tabelas com poucos registros não são utilizados de qualquer forma numa consulta, então eu resolvi ignora-las.

De qualquer forma, a consulta é uma ferramenta para ajudar o DBA a detectar uma possível falha de modelagem que pode estar causando problemas de performance.

Dicas para melhorar / corrigir as consultas são bem vindas.

Por hoje é só.


Fonte: http://feedproxy.google.com/~r/midstorm/~3/4ynKES56nlQ/

0sem comentários ainda

Enviar um comentário

Os campos são obrigatórios.

Se você é um usuário registrado, pode se identificar e ser reconhecido automaticamente.