Estatísticas no índice em partições não usadas na consulta em toda a tabela

9

A junção a seguir tem estimativas de linha muito diferentes ao fazer uma junção nas partições x ao ingressar em toda a tabela:

CREATE TABLE m_data.ga_session (
  session_id         BIGINT                   NOT NULL,
  visitor_id         BIGINT                   NOT NULL,
  transaction_id     TEXT,

  timestamp          TIMESTAMP WITH TIME ZONE NOT NULL,
  day_id             INTEGER                  NOT NULL,
  [...]

  device_category    TEXT                     NOT NULL,
  [...]
  operating_system   TEXT

);

Para todas as partições:

CREATE TABLE IF NOT EXISTS m_data.ga_session_20170127 ( CHECK (day_id = 20170127) ) INHERITS (m_data.ga_session);
-- the identifier are theoretically invalid, but they get truncated to 63 chars and nevertheless work
CREATE INDEX IF NOT EXISTS "ga_session__m_tmp.normalize_device_category(ga_session.device_category)" on m_data.ga_session_20170127 USING btree (m_tmp.normalize_device_category(device_category)) ;
CREATE INDEX IF NOT EXISTS "ga_session__m_tmp.normalize_operating_system(operating_system)" on m_data.ga_session_20170127 USING btree (m_tmp.normalize_operating_system(operating_system)) ;
ANALYZE m_data.ga_session_20170127;

EXPLAIN analyse
SELECT * 
  FROM m_data.ga_session_20170127 ga_session
    JOIN m_dim_next.device ON 
      device.device_category_name = m_tmp.normalize_device_category(ga_session.device_category)
      AND device.operating_system_name = m_tmp.normalize_operating_system(ga_session.operating_system);

As estatísticas para esses índices nas partições são visíveis:

SELECT * FROM pg_stats WHERE tablename ilike 'ga_session_20170127%';

schemaname |tablename                                                       |attname                    |inherited |null_frac   |avg_width |n_distinct   
-----------|----------------------------------------------------------------|---------------------------|----------|------------|----------|-------------
m_data     |ga_session_20170127__m_tmp.normalize_device_category(device_cat |normalize_device_category  |false     |0           |10        |3            
m_data     |ga_session_20170127__m_tmp.normalize_operating_system(operating |normalize_operating_system |false     |0           |7         |14           

Isso (com estatísticas do índice na partição) resulta nas seguintes estimativas (finas) do plano de consulta: 80146 estimado, 77503 real

Hash Join  (cost=1.95..6103.53 rows=80146 width=262) (actual time=0.121..117.204 rows=77503 loops=1)
  Hash Cond: ((COALESCE(initcap(ga_session.device_category), 'Unknown'::text) = device.device_category_name) AND (COALESCE(replace(ga_session.operating_system, '(not set)'::text, 'Unknown'::text), 'Unknown'::text) = device.operating_system_name))
  ->  Seq Scan on ga_session_20170127 ga_session  (cost=0.00..2975.03 rows=77503 width=224) (actual time=0.010..9.203 rows=77503 loops=1)
  ->  Hash  (cost=1.38..1.38 rows=38 width=38) (actual time=0.064..0.064 rows=38 loops=1)
        Buckets: 1024  Batches: 1  Memory Usage: 11kB
        ->  Seq Scan on device  (cost=0.00..1.38 rows=38 width=38) (actual time=0.006..0.019 rows=38 loops=1)
Planning time: 1.460 ms
Execution time: 120.098 ms

O que não funciona é a junção em toda a tabela, que estima uma contagem de linhas completamente incorreta (832 estimado versus 876237 real).

QUERY PLAN                                                                                                                                                                                                                                             
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Hash Join  (cost=1.95..60056.78 rows=832 width=262) (actual time=0.037..1065.778 rows=876237 loops=1)                                                                                                                                                  
  Hash Cond: ((COALESCE(initcap(ga_session.device_category), 'Unknown'::text) = device.device_category_name) AND (COALESCE(replace(ga_session.operating_system, '(not set)'::text, 'Unknown'::text), 'Unknown'::text) = device.operating_system_name)) 
  ->  Append  (cost=0.00..33759.37 rows=876238 width=225) (actual time=0.005..132.070 rows=876237 loops=1)                                                                                                                                             
        ->  Seq Scan on ga_session  (cost=0.00..0.00 rows=1 width=319) (actual time=0.000..0.000 rows=0 loops=1)                                                                                                                                       
        ->  Seq Scan on ga_session_20170125 ga_session_1  (cost=0.00..3648.38 rows=94438 width=226) (actual time=0.005..10.606 rows=94438 loops=1)                                                                                                     
        ->  Seq Scan on ga_session_20170126 ga_session_2  (cost=0.00..3185.81 rows=82581 width=225) (actual time=0.014..8.982 rows=82581 loops=1)                                                                                                      
        ->  Seq Scan on ga_session_20170127 ga_session_3  (cost=0.00..2975.03 rows=77503 width=224) (actual time=0.002..8.797 rows=77503 loops=1)                                                                                                      
        ->  Seq Scan on ga_session_20170128 ga_session_4  (cost=0.00..2936.83 rows=76083 width=225) (actual time=0.003..7.873 rows=76083 loops=1)                                                                                                      
        ->  Seq Scan on ga_session_20170129 ga_session_5  (cost=0.00..3716.18 rows=96618 width=224) (actual time=0.002..9.318 rows=96618 loops=1)                                                                                                      
        ->  Seq Scan on ga_session_20170130 ga_session_6  (cost=0.00..3833.19 rows=99619 width=224) (actual time=0.002..9.453 rows=99619 loops=1)                                                                                                      
        ->  Seq Scan on ga_session_20170131 ga_session_7  (cost=0.00..3488.79 rows=90579 width=225) (actual time=0.002..8.298 rows=90579 loops=1)                                                                                                      
        ->  Seq Scan on ga_session_20170201 ga_session_8  (cost=0.00..3615.58 rows=93958 width=224) (actual time=0.002..9.199 rows=93958 loops=1)                                                                                                      
        ->  Seq Scan on ga_session_20170202 ga_session_9  (cost=0.00..3286.56 rows=85256 width=224) (actual time=0.006..8.021 rows=85256 loops=1)                                                                                                      
        ->  Seq Scan on ga_session_20170203 ga_session_10  (cost=0.00..3073.02 rows=79602 width=225) (actual time=0.002..7.727 rows=79602 loops=1)                                                                                                     
  ->  Hash  (cost=1.38..1.38 rows=38 width=38) (actual time=0.016..0.016 rows=38 loops=1)                                                                                                                                                              
        Buckets: 1024  Batches: 1  Memory Usage: 11kB                                                                                                                                                                                                  
        ->  Seq Scan on device  (cost=0.00..1.38 rows=38 width=38) (actual time=0.002..0.004 rows=38 loops=1)                                                                                                                                          
Planning time: 1.017 ms                                                                                                                                                                                                                                
Execution time: 1090.213 ms   

Por sua vez, isso resulta em escolhas de junção incorretas (loops aninhados) quando o uso dessa junção resulta em mais junções (não mostradas aqui).

Na verdade, eu também tinha estimativas de linhas erradas nas partições antes de executá ANALYSE-las novamente, portanto, o planejador de consultas não leva em consideração as estatísticas baseadas em índices ao usar a tabela inteira.

Existe alguma maneira de fazer o planejador de consulta reunir estatísticas no nível da tabela pai ou levar em consideração as estatísticas individuais das partições ao criar o plano de consulta?

Jan Katins
fonte
2
A pergunta funcionaria melhor para mim se você fornecesse (as partes relevantes) suas definições reais de tabela e índice. Como você implementou partições? Com herança? Portanto, não existem índices criados em toda a tabela. Cada índice pode abranger apenas uma tabela física, ou seja, uma única partição.
Erwin Brandstetter
Desculpe, eu adicionei as informações. Os índices em si estão em cada tabela (fiquei confuso porque chamamos apenas uma função para criar o índice em todas as tabelas herdadas).
Jan Katins 22/02/19

Respostas:

1

Certifique-se de que não apenas as partições estejam indexadas, mas também a tabela mestre seja indexada da mesma maneira e ANALYZEd.

Isso pode fazer com que o planejador inclua estimativas baseadas em índices em uma única partição, mas as ignore no nível da tabela mestre.

Se o índice de expressão ou as estatísticas da tabela mestre estiverem ausentes, o planejador não poderá inferir a cardinalidade da junção a partir dessa condição - mesmo que possua estatísticas perfeitas para partições.

É apenas um palpite, porque você não forneceu o esquema completo. Por favor, deixe-me saber se isso ajuda.

filiprem
fonte