AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • Início
  • system&network
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • Início
  • system&network
    • Recentes
    • Highest score
    • tags
  • Ubuntu
    • Recentes
    • Highest score
    • tags
  • Unix
    • Recentes
    • tags
  • DBA
    • Recentes
    • tags
  • Computer
    • Recentes
    • tags
  • Coding
    • Recentes
    • tags
Início / dba / Perguntas / 154681
Accepted
Luan Huynh
Luan Huynh
Asked: 2016-11-10 00:47:58 +0800 CST2016-11-10 00:47:58 +0800 CST 2016-11-10 00:47:58 +0800 CST

Filtre no array text[] e classifique no carimbo de data/hora

  • 772

Descrição

PostgreSQL 9.6 no Linux, tamanho da tags_tmptabela ~ 30 GB (10 milhões de linhas), tagsé um text[]e tem apenas 6 valores.

tags_tmp(id int, tags text[], maker_date timestamp, value text)
id  tags        maker_date      value
1   {a,b,c}     2016-11-09      This is test 
2   {a}         2016-11-08      This is test 
3   {b,c}       2016-11-07      This is test 
4   {c}         2016-11-06      This is test 
5   {d}         2016-11-05      This is test 

Eu preciso recuperar dados com filtro ativado tagse também order byem maker_date desc. Posso criar um índice em ambas as tags & maker_date desccolunas?

Se não, você poderia sugerir outras ideias?

Exemplo de consulta

select id, tags, maker_date, value
from tags_tmp
where  tags && array['a','b']
order by maker_date desc
limit 5 offset 0

Código SQL:

create index idx1 on tags_tmp using gin (tags);
create index idx2 on tags_tmp using btree(maker_date desc);

explain (analyse on, costs on, verbose)
select id, tags, maker_date, value
from tags_tmp
where tags && array['funny','inspiration']
order by maker_date desc
limit 5 offset 0 ;

Explique o resultado:

Limit  (cost=233469.63..233469.65 rows=5 width=116) (actual time=801.482..801.483 rows=5 loops=1)
  Output: id, tags, maker_date, value
  ->  Sort  (cost=233469.63..234714.22 rows=497833 width=116) (actual time=801.481..801.481 rows=5 loops=1)
        Output: id, tags, maker_date, value
        Sort Key: tags_tmp.maker_date DESC
        Sort Method: top-N heapsort  Memory: 25kB
        ->  Bitmap Heap Scan on public.tags_tmp  (cost=6486.58..225200.81 rows=497833 width=116) (actual time=212.982..696.650 rows=366392 loops=1)
              Output: id, tags, maker_date, value
              Recheck Cond: (tags_tmp.tags && '{funny,inspiration}'::text[])
              Heap Blocks: exact=120034
              ->  Bitmap Index Scan on idx1  (cost=0.00..6362.12 rows=497882 width=0) (actual time=171.742..171.742 rows=722612 loops=1)
                    Index Cond: (tags_tmp.tags && '{funny,inspiration}'::text[])
Planning time: 0.185 ms
Execution time: 802.128 ms

Mais Informações

Testei usando índice parcial para apenas uma tag, claro, é mais rápido. Mas eu tenho muitos tag , por exemplo: create index idx_tmp on tags_tmp using btree (maker_date desc) where (tags && array['tag1') or tags && array['tag2'] or ... or tags && array['tag6']. E eu testei entre tags && array['tag1']e 'tag1' = any(tags), o desempenho é o mesmo.

  1. text[]tem apenas 6 valores = a, b, c, d, e, f. Por exemplo: tags={a,b,c}, tags={a}, tags={a,c}, tags={a,b,c,d,e,f}, tags={b,f}e assim por diante. Mas não pode ter valor g->z, A-Ze etc.

  2. create table tags_tmp(id int primary key not null, tags text[] not null, maker_date timestamp not null, value text)

  3. Em termos de distinctvalores de matriz, o tagsque contém aleva 20% das linhas da tabela where 'a' = any(tags), b=20% where 'b' = any(tags), c=20% where 'c' = any(tags), d=20% where 'd' = any(tags), e=10% where 'e' = any(tags), f=10% where 'f' = any(tags).

  4. Além disso, (tags, maker_date)não é único.

  5. Esta tabela não é somente leitura.

  6. É sort on timestamp, mas meu exemplo mostra datas, desculpe por isso.

Situação atual: tags = 'a' or tags = 'b' or tags = 'c'e mais

(1) Com GIN indexou converter text[] to int[], bem como converter text[] to inte mais, ele usará o índice de bitmap em várias tags. Finalmente, após o teste, decidi usar a solução antiga, mudar ORpara muitas UNIONcláusulas, cada UNIONuma limitando o número de dados. Claro, vou criar partial indexpara cada valor de tag, assim como posso combinar com (1) acima. Em termos de OFFSET, ele usará uma ou mais condições na WHEREcláusula.

Exemplo

EXPLAIN (ANALYSE ON, costs ON, VERBOSE)
SELECT rs.*
FROM (
        (SELECT tags,
                id,
                maker_date
         FROM tags_tmp
         WHERE 'a' = any(tags)
           AND maker_date <= '2016-03-28 05:43:57.779528'::TIMESTAMP
         ORDER BY maker_date DESC LIMIT 5)
      UNION
        (SELECT tags,
                id,
                maker_date
         FROM tags_tmp
         WHERE 'b' = any(tags)
           AND maker_date <= '2016-03-28 05:43:57.779528'::TIMESTAMP
         ORDER BY maker_date DESC LIMIT 5)
      UNION
        (SELECT tags,
                id,
                maker_date
         FROM tags_tmp
         WHERE 'c' = any(tags)
           AND maker_date <= '2016-03-28 05:43:57.779528'::TIMESTAMP
         ORDER BY maker_date DESC LIMIT 5)) rs
ORDER BY rs.maker_date DESC LIMIT 5 ;
postgresql index-tuning
  • 2 2 respostas
  • 8086 Views

2 respostas

  • Voted
  1. Best Answer
    Erwin Brandstetter
    2016-11-12T19:43:16+08:002016-11-12T19:43:16+08:00

    Considerações gerais

    A otimização do índice sempre depende da imagem completa . Tamanho da tabela, tamanho da linha, cardinalidades, frequências de valor, seletividade de consultas típicas, versão do Postgres, padrões de acesso típicos, etc.

    Seu caso é particularmente difícil por dois motivos:

    1. Colunas diferentes usadas em WHEREe ORDER BY.
    2. O filtro na matriz é mais eficiente com o índice GIN ou GiST, mas nenhum dos tipos de índice produz uma saída classificada. O manual:

      Dos tipos de índice atualmente suportados pelo PostgreSQL, apenas a árvore B pode produzir saída classificada — os outros tipos de índice retornam linhas correspondentes em uma ordem não especificada e dependente da implementação.

    Você pode criar um índice GIN de várias colunas em (tags, maker_date)ou até mais colunas (a ordem das colunas do índice é irrelevante para índices GIN). Mas você precisa ter o módulo adicional btree_gininstalado. Instruções:

    • Junção interna usando uma coluna de matriz

    E não vai ajudar na parte ORDER BYdo seu problema.

    Mais um esclarecimento: OFFSET m LIMIT ngeralmente é quase tão caro quanto LIMIT m+n.

    Solução para especificações adicionadas

    Você esclareceu: apenas 6 tags distintas são possíveis. Isso é crucial.

    Sua mesa é grande e sua definição de mesa deixa espaço para melhorias. O tamanho é importante para mesas grandes. Seus números (30 GB, 10 milhões de linhas) também sugerem uma grande média. tamanho da linha de ~ 3 KB. Ou você tem mais colunas do que mostra ou a tabela incha e precisa de uma VACUUM FULLexecução (ou similar) ou sua valuecoluna é grande e TOASTada, o que tornaria minhas melhorias ainda mais eficazes, pois a relação principal é reduzida à metade de seu tamanho ou menos com isso :

    CREATE TABLE tags_tmp (
      id         int PRIMARY KEY -- assuming PK
    , tags       int NOT NULL    -- also assuming NOT NULL
    , value      text
    , maker_date timestamp NOT NULL  -- NOT NULL!
    );
    

    A ordem das colunas é relevante devido ao preenchimento de alinhamento. Detalhes:

    • Configurando o PostgreSQL para desempenho de leitura

    Mais importante, isto: tags int. Por quê?

    As matrizes têm uma sobrecarga considerável de 24 bytes (semelhante a uma linha), mais itens reais.

    • Calculando e economizando espaço no PostgreSQL

    Portanto, um text[]com 1-6 itens como você demonstra ('engraçado', 'inspiração', ...) ocupa ~ 56 bytes em avg . E 6 valores distintos podem ser representados por apenas 6 bits de informação (supondo que a ordem de classificação do array seja irrelevante). Poderíamos comprimir ainda mais, mas escolhi o integertipo conveniente (ocupa 4 bytes ), que oferece espaço para até 31 tags distintas. Isso deixa espaço para adições posteriores sem alterações no esquema da tabela. Justificativa detalhada:

    • Devo usar a string de bits do PostgreSQL?

    Suas tags são mapeadas para bits em um bitmap, 'a'sendo o bit menos significativo (à direita):

    tag:       a | b | c | d |  e |  f
    position:  0 | 1 | 2 | 3 |  4 |  5
    int value: 1 | 2 | 4 | 8 | 16 | 32
    

    Portanto, a matriz de tags '{a,d,f}'é mapeada para 41. Você pode usar qualquer string arbitrária em vez de 'a'-'f', não importa.

    Para encapsular a lógica sugiro duas funções auxiliares , facilmente expansíveis:

    tags -> inteiro:

    CREATE OR REPLACE FUNCTION f_tags2int(text[])
      RETURNS int AS
    $func$
    SELECT bit_or(CASE x
                WHEN 'a' THEN  1
                WHEN 'b' THEN  2
                WHEN 'c' THEN  4
                WHEN 'd' THEN  8
                WHEN 'e' THEN 16
                WHEN 'f' THEN 32
                -- more?
               END)
    FROM    unnest ($1) x
    $func$  LANGUAGE SQL IMMUTABLE;
    

    inteiro -> tags:

    CREATE OR REPLACE FUNCTION f_int2tags(int)
      RETURNS text[] AS
    $func$
    SELECT array_remove(ARRAY [CASE WHEN $1 &  1 > 0 THEN 'a' END
                             , CASE WHEN $1 &  2 > 0 THEN 'b' END
                             , CASE WHEN $1 &  4 > 0 THEN 'c' END
                             , CASE WHEN $1 &  8 > 0 THEN 'd' END
                             , CASE WHEN $1 & 16 > 0 THEN 'e' END
                             , CASE WHEN $1 & 32 > 0 THEN 'f' END], NULL)
                             -- more? 
    $func$  LANGUAGE SQL IMMUTABLE;
    

    Básico aqui:

    • Posso converter um monte de colunas booleanas em um único bitmap no PostgreSQL?

    Por conveniência, você pode adicionar uma visualização para exibir tags como matriz de texto como você tinha:

    CREATE VIEW tags_tmp_pretty AS
    SELECT id, tags
         , f_int2tags(tags) AS tags_pretty
         , maker_date, value
    FROM   tags_tmp;
    

    Agora sua consulta básica pode ser:

    SELECT id, tags, maker_date, value
    FROM   tags_tmp
    WHERE  tags & f_tags2int('{a,b}') > 0  -- any of the tags matched
    ORDER  by maker_date DESC
    LIMIT  5;
    

    Usando o operador AND binário& . Existem mais operadores para manipular a coluna. get_bit()e set_bit()dos operadores de cadeia binária também são convenientes.

    A consulta acima já deveria ser mais rápida, apenas para as operadoras de menor porte e mais baratas, mas nada revolucionário ainda. Para torná-lo rápido , precisamos de índices, e o acima não pode usar um índice ainda.

    Um índice parcial para cada tag:

    CREATE INDEX foo_tag_a ON tags_tmp(maker_date DESC) WHERE tags & 1 > 0;
    CREATE INDEX foo_tag_b ON tags_tmp(maker_date DESC) WHERE tags & 2 > 0;
    ...
    CREATE INDEX foo_tag_f ON tags_tmp(maker_date DESC) WHERE tags & 32 > 0;
    

    Esta consulta é equivalente à anterior, mas pode utilizar os índices:

    SELECT *
    FROM   tags_tmp_pretty
    WHERE (tags & f_tags2int('{a}') > 0   -- same as tags & 1
        OR tags & f_tags2int('{e}') > 0)  -- same as tags & 32
    ORDER  BY maker_date DESC
    LIMIT  10;
    

    Postgres pode combinar várias varreduras de índice de bitmap em uma BitmapOretapa de forma muito eficiente, como demonstrado neste SQL Fiddle .

    Você pode adicionar outra condição de índice para limitar os índices a maker_date> algum registro de data e hora constante (e repetir a condição literal nas consultas) para reduzir seu tamanho (massivamente). Exemplo relacionado:

    • Otimização do índice com datas

    Mais sofisticado:

    • Adicionar restrição de data e hora a um índice parcial de várias colunas do PostgreSQL

    Outras respostas relacionadas:

    • Como acelerar a classificação ORDER BY ao usar o índice GIN no PostgreSQL?

    • O índice espacial pode ajudar uma consulta "range - order by - limit"

    Ou apenas 6 booleancolunas ...

    Apenas 6 colunas booleanas podem ser uma escolha ainda melhor. Existem alguns prós e contras para qualquer solução ...

    CREATE TABLE tags_tmp (
      id         int PRIMARY KEY -- assuming PK
    , tag_a      bool 
    , tag_b      bool 
      ...
    , tag_f      bool 
    , value      text
    , maker_date timestamp NOT NULL  -- NOT NULL!
    );
    

    Você pode definir os sinalizadores NOT NULL, dependendo do seu caso de uso completo.

    Considerar:

    • Devo usar a string de bits do PostgreSQL?

    Índices parciais simplesmente:

    CREATE INDEX foo_tag_a ON tags_tmp(maker_date DESC) WHERE tag_a;
    CREATE INDEX foo_tag_b ON tags_tmp(maker_date DESC) WHERE tag_b;
    

    etc.

    Alternativa para o seu caso especial

    Pensando um pouco mais, já que todas as suas poucas tags são tão comuns , e combinar várias tags com OR é ainda menos seletivo, será mais rápido ter apenas um índice btree em maker_date DESC. O Postgres pode percorrer o índice e filtrar as linhas qualificadas nas tags. Isso funcionará em combinação com colunas booleanas separadas em vez da matriz ou inteiro codificado, porque o Postgres tem estatísticas de colunas mais úteis para colunas separadas.

    CREATE INDEX tags_tmp_date ON tags_tmp(maker_date DESC);
    

    E depois:

    SELECT *
    FROM   tags_tmp_pretty
    WHERE  tag_a
       OR  tag_b
    ORDER  BY maker_date DESC
    LIMIT  10;
    

    Paginação

    Você precisa de uma ordem de classificação inequívoca para conjuntos de resultados, para fazer a paginação funcionar. Não me preocupei com esta resposta, já é muito longa. Normalmente, você adicionaria mais colunas a ORDER BY. Como fazer a paginação funcionar de forma eficiente com isso:

    • Otimize a consulta com OFFSET em uma tabela grande
    • 6
  2. Erwin Brandstetter
    2016-11-17T22:17:47+08:002016-11-17T22:17:47+08:00

    Vários problemas com seu caso de teste:

    1. idé int8agora. Você declarou como intna sua pergunta original Não é uma grande diferença, mas por que a confusão para começar? É importante para o tamanho da linha e preenchimento de alinhamento. Lembre-se de declarar sua definição de tabela real, exata e completa nas perguntas.

    2. A distribuição de dados em seus dados de teste não é realista. Você tem apenas 6 combinações distintas de tags e *todas as linhas têm tag '1'. Presumo que você tenha todas as 63 combinações possíveis em sua mesa ao vivo, com tags distribuídas como você adicionou na pergunta.

    3. Sua tabela de teste inclui colunas de tags antigas e novas, o que anula o efeito no tamanho de armazenamento que eu procurava. Agora o tamanho da linha é ainda maior. O tamanho da sua linha é de 124 a 164 bytes, contra apenas 68 bytes no meu teste (incluindo preenchimento e identificador de item). Mais de duas vezes o tamanho faz a diferença.

    4. Você escreve tamanho = 4163 MB . Que tamanho?

    5. Você tem order by random()para os dados de teste. Sua mesa produtiva é tão aleatória assim? Normalmente, você teria dados aproximadamente classificados por carimbo de data/hora. Qual é a sua situação real ?

    6. Para ver qual plano será escolhido, teste EXPLAINapenas para ver o plano de consulta antes de realmente executar a consulta. Economiza muito tempo com mesas grandes. Mas sempre forneça a saída EXPLAIN (ANALYZE, BUFFERS)daqui. Em sua resposta (ao contrário da pergunta), cost=faltam estimativas. Isso torna difícil adivinhar o problema.

    Mas nenhum desses problemas pode explicar por que você vê uma varredura sequencial mesmo com enable_seqscan = off; Um teste rápido no meu laptop com Postgres 9.5 funciona . O mesmo deve ser verdade para a página 9.6.

    CREATE TABLE tags_tmp(
       id         bigserial PRIMARY KEY, 
       maker_date timestamp NOT NULL,
       tags       int NOT NULL,
       value      text
    );
    
    INSERT INTO tags_tmp (tags, maker_date, value)
    SELECT EXTRACT('minute' FROM ts)::int    -- int between 1 and 60 (no 61,62,63), pretty good.
         , ts + random() * interval '5 min'  -- some limited randomness
         , 'This is test on ' || EXTRACT('minute' FROM ts)
    FROM   generate_series(timestamp '2016-01-01 00:00'
                         , timestamp '2016-01-13 00:00', '10 second') ts;
    -- 103681 rows affected, 836 msec execution time.
    
    -- create adapted function f_tags2int
    -- create adapted function f_int2tags
    
    CREATE INDEX tags_tmp_1 ON tags_tmp(maker_date DESC) WHERE tags &  1 > 0;
    CREATE INDEX tags_tmp_2 ON tags_tmp(maker_date DESC) WHERE tags &  2 > 0;
    CREATE INDEX tags_tmp_3 ON tags_tmp(maker_date DESC) WHERE tags &  4 > 0;
    CREATE INDEX tags_tmp_4 ON tags_tmp(maker_date DESC) WHERE tags &  8 > 0;
    CREATE INDEX tags_tmp_5 ON tags_tmp(maker_date DESC) WHERE tags & 16 > 0;
    CREATE INDEX tags_tmp_6 ON tags_tmp(maker_date DESC) WHERE tags & 32 > 0;
    
    SELECT id, tags, maker_date, value
    FROM   tags_tmp
    WHERE (tags & f_tags2int(array['5']) > 0 OR
           tags & f_tags2int(array['6']) > 0)
    ORDER  BY maker_date DESC
    LIMIT  5;
    
    QUERY PLAN
    Limit  (cost=3811.93..3811.94 rows=5 width=38) (actual time=46.586..46.586 rows=5 loops=1)
      Buffers: shared hit=1132
      ->  Sort  (cost=3811.93..3955.93 rows=57601 width=38) (actual time=46.584..46.585 rows=5 loops=1)
            Sort Key: maker_date DESC
            Sort Method: top-N heapsort  Memory: 25kB
            Buffers: shared hit=1132
            ->  Bitmap Heap Scan on tags_tmp  (cost=607.78..2855.20 rows=57601 width=38) (actual time=13.699..27.674 rows=76032 loops=1)
                  Recheck Cond: (((tags & 16) > 0) OR ((tags & 32) > 0))
                  Heap Blocks: exact=864
                  Buffers: shared hit=1132
                  ->  BitmapOr  (cost=607.78..607.78 rows=69121 width=0) (actual time=13.549..13.549 rows=0 loops=1)
                        Buffers: shared hit=268
                        ->  Bitmap Index Scan on tags_tmp_5 cost=0.00..289.49 rows=34560 width=0) (actual time=8.745..8.745 rows=48384 loops=1)
                              Buffers: shared hit=134
                        ->  Bitmap Index Scan on tags_tmp_6 (cost=0.00..289.49 rows=34560 width=0) (actual time=4.800..4.800 rows=48384 loops=1)
                              Buffers: shared hit=134
    Planning time: 3.976 ms
    Execution time: 46.653 ms
    

    Assim como já demonstrei no SQL Fiddle .

    Tem certeza de que criou todos os índices corretamente?

    • 1

relate perguntas

  • Posso ativar o PITR depois que o banco de dados foi usado

  • Práticas recomendadas para executar a replicação atrasada do deslocamento de tempo

  • Os procedimentos armazenados impedem a injeção de SQL?

  • Sequências Biológicas do UniProt no PostgreSQL

  • Qual é a diferença entre a replicação do PostgreSQL 9.0 e o Slony-I?

Sidebar

Stats

  • Perguntas 205573
  • respostas 270741
  • best respostas 135370
  • utilizador 68524
  • Highest score
  • respostas
  • Marko Smith

    conectar ao servidor PostgreSQL: FATAL: nenhuma entrada pg_hba.conf para o host

    • 12 respostas
  • Marko Smith

    Como fazer a saída do sqlplus aparecer em uma linha?

    • 3 respostas
  • Marko Smith

    Selecione qual tem data máxima ou data mais recente

    • 3 respostas
  • Marko Smith

    Como faço para listar todos os esquemas no PostgreSQL?

    • 4 respostas
  • Marko Smith

    Listar todas as colunas de uma tabela especificada

    • 5 respostas
  • Marko Smith

    Como usar o sqlplus para se conectar a um banco de dados Oracle localizado em outro host sem modificar meu próprio tnsnames.ora

    • 4 respostas
  • Marko Smith

    Como você mysqldump tabela (s) específica (s)?

    • 4 respostas
  • Marko Smith

    Listar os privilégios do banco de dados usando o psql

    • 10 respostas
  • Marko Smith

    Como inserir valores em uma tabela de uma consulta de seleção no PostgreSQL?

    • 4 respostas
  • Marko Smith

    Como faço para listar todos os bancos de dados e tabelas usando o psql?

    • 7 respostas
  • Martin Hope
    Jin conectar ao servidor PostgreSQL: FATAL: nenhuma entrada pg_hba.conf para o host 2014-12-02 02:54:58 +0800 CST
  • Martin Hope
    Stéphane Como faço para listar todos os esquemas no PostgreSQL? 2013-04-16 11:19:16 +0800 CST
  • Martin Hope
    Mike Walsh Por que o log de transações continua crescendo ou fica sem espaço? 2012-12-05 18:11:22 +0800 CST
  • Martin Hope
    Stephane Rolland Listar todas as colunas de uma tabela especificada 2012-08-14 04:44:44 +0800 CST
  • Martin Hope
    haxney O MySQL pode realizar consultas razoavelmente em bilhões de linhas? 2012-07-03 11:36:13 +0800 CST
  • Martin Hope
    qazwsx Como posso monitorar o andamento de uma importação de um arquivo .sql grande? 2012-05-03 08:54:41 +0800 CST
  • Martin Hope
    markdorison Como você mysqldump tabela (s) específica (s)? 2011-12-17 12:39:37 +0800 CST
  • Martin Hope
    Jonas Como posso cronometrar consultas SQL usando psql? 2011-06-04 02:22:54 +0800 CST
  • Martin Hope
    Jonas Como inserir valores em uma tabela de uma consulta de seleção no PostgreSQL? 2011-05-28 00:33:05 +0800 CST
  • Martin Hope
    Jonas Como faço para listar todos os bancos de dados e tabelas usando o psql? 2011-02-18 00:45:49 +0800 CST

Hot tag

sql-server mysql postgresql sql-server-2014 sql-server-2016 oracle sql-server-2008 database-design query-performance sql-server-2017

Explore

  • Início
  • Perguntas
    • Recentes
    • Highest score
  • tag
  • help

Footer

AskOverflow.Dev

About Us

  • About Us
  • Contact Us

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve