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 / 190533
Accepted
Parker
Parker
Asked: 2017-11-10 06:03:50 +0800 CST2017-11-10 06:03:50 +0800 CST 2017-11-10 06:03:50 +0800 CST

Consulta lenta no PostgreSQL selecionando uma única linha entre um intervalo definido em duas colunas

  • 772

Importei uma cópia do banco de dados ip2location_db11 lite , que contém 3.319.097 linhas, e estou procurando otimizar uma consulta de intervalo numérico, onde os valores baixo e alto estão em colunas separadas da tabela ( ip_from, ip_to).

Importando o banco de dados:

CREATE TABLE ip2location_db11
(
  ip_from bigint NOT NULL, -- First IP address in netblock.
  ip_to bigint NOT NULL, -- Last IP address in netblock.
  country_code character(2) NOT NULL, -- Two-character country code based on ISO 3166.
  country_name character varying(64) NOT NULL, -- Country name based on ISO 3166.
  region_name character varying(128) NOT NULL, -- Region or state name.
  city_name character varying(128) NOT NULL, -- City name.
  latitude real NOT NULL, -- City latitude. Default to capital city latitude if city is unknown.
  longitude real NOT NULL, -- City longitude. Default to capital city longitude if city is unknown.
  zip_code character varying(30) NOT NULL, -- ZIP/Postal code.
  time_zone character varying(8) NOT NULL, -- UTC time zone (with DST supported).
  CONSTRAINT ip2location_db11_pkey PRIMARY KEY (ip_from, ip_to)
);
\copy ip2location_db11 FROM 'IP2LOCATION-LITE-DB11.CSV' WITH CSV QUOTE AS '"';

Minha primeira tentativa de indexação ingênua foi criar índices separados em cada uma dessas colunas, o que resultou em uma varredura sequencial com tempos de consulta de 400ms:

account=> CREATE INDEX ip_from_db11_idx ON ip2location_db11 (ip_from);
account=> CREATE INDEX ip_to_db11_idx ON ip2location_db11 (ip_to);

account=> EXPLAIN ANALYZE VERBOSE SELECT * FROM ip2location_db11 WHERE 2538629520 BETWEEN ip_from AND ip_to;

                                                          QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------------------------------
 Seq Scan on public.ip2location_db11  (cost=0.00..48930.99 rows=43111 width=842) (actual time=286.714..401.805 rows=1 loops=1)
   Output: ip_from, ip_to, country_code, country_name, region_name, city_name, latitude, longitude, zip_code, time_zone
   Filter: (('2538629520'::bigint >= ip2location_db11.ip_from) AND ('2538629520'::bigint <= ip2location_db11.ip_to))
   Rows Removed by Filter: 3319096
 Planning time: 0.155 ms
 Execution time: 401.834 ms
(6 rows)

account=> \d ip2location_db11
          Table "public.ip2location_db11"
    Column    |          Type          | Modifiers
--------------+------------------------+-----------
 ip_from      | bigint                 | not null
 ip_to        | bigint                 | not null
 country_code | character(2)           | not null
 country_name | character varying(64)  | not null
 region_name  | character varying(128) | not null
 city_name    | character varying(128) | not null
 latitude     | real                   | not null
 longitude    | real                   | not null
 zip_code     | character varying(30)  | not null
 time_zone    | character varying(8)   | not null
Indexes:
    "ip2location_db11_pkey" PRIMARY KEY, btree (ip_from, ip_to)
    "ip_from_db11_idx" btree (ip_from)
    "ip_to_db11_idx" btree (ip_to)

Minha segunda tentativa foi criar um índice btree de várias colunas, que resultou em uma verificação de índice com tempos de consulta de 290ms:

account=> CREATE INDEX ip_range_db11_idx ON ip2location_db11 (ip_from,ip_to);

account=> EXPLAIN ANALYZE VERBOSE SELECT * FROM ip2location_db11 WHERE 2538629520 BETWEEN ip_from AND ip_to;
                                                                     QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------------------------------
 Index Scan using ip_to_db11_idx on public.ip2location_db11 (cost=0.43..51334.91 rows=756866 width=69) (actual time=1.109..289.143 rows=1 loops=1)
   Output: ip_from, ip_to, country_code, country_name, region_name, city_name, latitude, longitude, zip_code, time_zone
   Index Cond: ('2538629520'::bigint <= ip2location_db11.ip_to)
   Filter: ('2538629520'::bigint >= ip2location_db11.ip_from)
   Rows Removed by Filter: 1160706
 Planning time: 0.324 ms
 Execution time: 289.172 ms
(7 rows)

n4l_account=> \d ip2location_db11
          Table "public.ip2location_db11"
    Column    |          Type          | Modifiers
--------------+------------------------+-----------
 ip_from      | bigint                 | not null
 ip_to        | bigint                 | not null
 country_code | character(2)           | not null
 country_name | character varying(64)  | not null
 region_name  | character varying(128) | not null
 city_name    | character varying(128) | not null
 latitude     | real                   | not null
 longitude    | real                   | not null
 zip_code     | character varying(30)  | not null
 time_zone    | character varying(8)   | not null
Indexes:
    "ip2location_db11_pkey" PRIMARY KEY, btree (ip_from, ip_to)
    "ip_from_db11_idx" btree (ip_from)
    "ip_range_db11_idx" btree (ip_from, ip_to)
    "ip_to_db11_idx" btree (ip_to)

Atualização : Conforme solicitado nos comentários, refazer a consulta acima. O tempo das primeiras 15 consultas após a recriação da tabela (165ms, 65ms, 86ms, 83ms, 86ms, 64ms, 85ms, 811ms, 868ms, 845ms, 810ms, 781ms, 797ms, 890ms, 806ms):

account=> EXPLAIN (ANALYZE, VERBOSE, BUFFERS, TIMING) SELECT * FROM ip2location_db11 WHERE 2538629520 BETWEEN ip_from AND ip_to;
                                                                QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------------------
 Bitmap Heap Scan on public.ip2location_db11  (cost=28200.29..76843.12 rows=368789 width=842) (actual time=64.866..64.866 rows=1 loops=1)
   Output: ip_from, ip_to, country_code, country_name, region_name, city_name, latitude, longitude, zip_code, time_zone
   Recheck Cond: (('2538629520'::bigint >= ip2location_db11.ip_from) AND ('2538629520'::bigint <= ip2location_db11.ip_to))
   Heap Blocks: exact=1
   Buffers: shared hit=8273
   ->  Bitmap Index Scan on ip_range_db11_idx  (cost=0.00..28108.09 rows=368789 width=0) (actual time=64.859..64.859 rows=1 loops=1)
         Index Cond: (('2538629520'::bigint >= ip2location_db11.ip_from) AND ('2538629520'::bigint <= ip2location_db11.ip_to))
         Buffers: shared hit=8272
 Planning time: 0.099 ms
 Execution time: 64.907 ms
(10 rows)

account=> EXPLAIN (ANALYZE, VERBOSE, BUFFERS, TIMING) SELECT * FROM ip2location_db11 WHERE 2538629520 BETWEEN ip_from AND ip_to;
                                                          QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------------
 Seq Scan on public.ip2location_db11  (cost=0.00..92906.18 rows=754776 width=69) (actual time=577.234..811.757 rows=1 loops=1)
   Output: ip_from, ip_to, country_code, country_name, region_name, city_name, latitude, longitude, zip_code, time_zone
   Filter: (('2538629520'::bigint >= ip2location_db11.ip_from) AND ('2538629520'::bigint <= ip2location_db11.ip_to))
   Rows Removed by Filter: 3319096
   Buffers: shared hit=33 read=43078
 Planning time: 0.667 ms
 Execution time: 811.783 ms
(7 rows)

Linhas de amostra do arquivo CSV importado:

"0","16777215","-","-","-","-","0.000000","0.000000","-","-"
"16777216","16777471","AU","Australia","Queensland","Brisbane","-27.467940","153.028090","4000","+10:00"
"16777472","16778239","CN","China","Fujian","Fuzhou","26.061390","119.306110","350004","+08:00"

Existe uma maneira melhor de indexar esta tabela que melhoraria a consulta ou existe uma consulta mais eficiente que me daria o mesmo resultado?

postgresql performance
  • 3 3 respostas
  • 928 Views

3 respostas

  • Voted
  1. Best Answer
    Kent Chenery
    2017-11-11T01:59:15+08:002017-11-11T01:59:15+08:00

    Esta é uma solução um pouco diferente daquelas já oferecidas que envolviam o uso de índices espaciais para fazer alguns truques.

    Em vez disso, vale a pena lembrar que, com endereços IP, você não pode ter intervalos sobrepostos. Isso A -> Bnão pode se cruzar X -> Yde forma alguma. Sabendo disso, você pode alterar um SELECTpouco sua consulta e aproveitar essa característica. Ao tirar proveito dessa característica, você não precisa ter nenhuma indexação "inteligente". Na verdade, você só precisa indexar sua ip_fromcoluna.

    Anteriormente, a consulta que estava sendo analisada era:

    SELECT * FROM ip2location_db11 WHERE 2538629520 BETWEEN ip_from AND ip_to;
    

    Vamos supor que o intervalo que 2538629520se enquadra seja 2538629512e 2538629537.

    Nota: Realmente não importa qual seja o intervalo, isso é apenas para ajudar a demonstrar o padrão do qual podemos aproveitar.

    A partir disso, podemos supor que o próximo ip_fromvalor é 2538629538. Na verdade, não precisamos nos preocupar com nenhum registro acima desse ip_fromvalor. Na verdade, tudo o que realmente importa é o intervalo onde ip_from é igual 2538629512 a .

    Sabendo desse fato, nossa consulta realmente se torna (em inglês):

    Encontre-me o ip_fromvalor máximo em que meu endereço IP é maior que ip_from. Mostre-me o registro onde você encontra esse valor.

    Ou em outras palavras: encontre-me o ip_fromvalor logo antes do meu endereço IP e me dê esse registro

    Como nunca temos intervalos sobrepostos, ip_fromisso ip_toé verdade e nos permite escrever a consulta como:

    SELECT * 
    FROM ip2location
    WHERE ip_from = (
        SELECT MAX(ip_from)
        FROM ip2location
        WHERE ip_from <= 2538629520
        )
    

    Volte para a indexação para aproveitar tudo isso. Na verdade, tudo o que estamos vendo é ip_from e estamos fazendo comparações de inteiros. O MIN(ip_from) faz com que o PostgreSQL encontre o primeiro registro disponível. Isso é bom porque podemos buscar isso e não nos preocuparmos com nenhum outro registro.

    Tudo o que realmente precisamos é de um índice como:

    CREATE UNIQUE INDEX CONCURRENTLY ix_ip2location_ipFrom ON public.ip2location(ip_from)

    Podemos tornar o índice único porque não teremos registros sobrepostos. Eu mesmo tornaria essa coluna a chave primária.

    Com este índice e esta consulta, o plano de explicação é:

    Index Scan using ix_ip2location_ipfrom on public.ip2location  (cost=0.90..8.92 rows=1 width=69) (actual time=0.530..0.533 rows=1 loops=1)
    Output: ip2location.ip_from, ip2location.ip_to, ip2location.country_code, ip2location.country_name, ip2location.region_name, ip2location.city_name, ip2location.latitude, ip2location.longitude, ip2location.zip_code, ip2location.time_zone
    Index Cond: (ip2location.ip_from = $1)
    InitPlan 2 (returns $1)
        ->  Result  (cost=0.46..0.47 rows=1 width=8) (actual time=0.452..0.452 rows=1 loops=1)
            Output: $0
            InitPlan 1 (returns $0)
                ->  Limit  (cost=0.43..0.46 rows=1 width=8) (actual time=0.443..0.444 rows=1 loops=1)
                    Output: ip2location_1.ip_from
                    ->  Index Only Scan using ix_ip2location_ipfrom on public.ip2location ip2location_1  (cost=0.43..35440.79 rows=1144218 width=8) (actual time=0.438..0.438 rows=1 loops=1)
                            Output: ip2location_1.ip_from
                            Index Cond: ((ip2location_1.ip_from IS NOT NULL) AND (ip2location_1.ip_from >= '2538629520'::bigint))
                            Heap Fetches: 0
    

    Para dar uma ideia de melhoria no desempenho da consulta com essa abordagem, testei isso no meu Raspberry Pi. A abordagem original levou aproximadamente 4 segundos. Essa abordagem leva aproximadamente 120ms. A grande vitória é da fila individual procura versos alguns scans. A consulta original sofreria EXTREMAMENTE com valores de intervalo baixos, pois mais da tabela precisa ser considerada nos resultados. Essa consulta exibirá um desempenho consistente em todo o intervalo de valores.

    Espero que isso ajude e minha explicação faça sentido para todos vocês.

    • 5
  2. Parker
    2017-11-10T06:28:13+08:002017-11-10T06:28:13+08:00

    Graças a um comentário, tenho uma solução que reduziu o tempo de consulta para 0,073ms usando um índice espacial gist e ajustando a consulta de acordo:

    account=> DROP INDEX ip_to_db11_idx;
    account=> DROP INDEX ip_from_db11_idx;
    account=> DROP INDEX ip_range_db11_idx;
    account=> CREATE INDEX ip2location_db11_gist ON ip2location_db11 USING gist ((box(point(ip_from,ip_from),point(ip_to,ip_to))) box_ops);
    
    account=> EXPLAIN ANALYZE VERBOSE SELECT * FROM ip2location_db11 WHERE  box(point(ip_from,ip_from),point(ip_to,ip_to)) @> box(point (2538629520,2538629520), point(2538629520,2538629520));
    
    
                  QUERY PLAN
    ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
     Bitmap Heap Scan on public.ip2location_db11  (cost=190.14..10463.13 rows=3319 width=69) (actual time=0.032..0.033 rows=1 loops=1)
       Output: ip_from, ip_to, country_code, country_name, region_name, city_name, latitude, longitude, zip_code, time_zone
       Recheck Cond: (box(point((ip2location_db11.ip_from)::double precision, (ip2location_db11.ip_from)::double precision),
     point((ip2location_db11.ip_to)::double precision, (ip2location_db11.ip_to)::double precision)) @> '(2538629520,2538629520),(2538629520,2538629520)'::box)
       Heap Blocks: exact=1
       ->  Bitmap Index Scan on ip2location_db11_gist  (cost=0.00..189.31 rows=3319 width=0) (actual time=0.022..0.022 rows=1 loops=1)
             Index Cond: (box(point((ip2location_db11.ip_from)::double precision, (ip2location_db11.ip_from)::double precision), point((ip2location_db11.ip_to)::double precision, (ip2location_db11.ip_to)::double precision)) @> '(2538629520,2538629520),(2538629520,2538629520)'::box)
     Planning time: 2.119 ms
     Execution time: 0.073 ms
    (8 rows)
    

    Citações:

    http://www.siafoo.net/article/53#comment_288

    http://www.pgsql.cz/index.php/PostgreSQL_SQL_Tricks#Fast_interval_.28of_time_or_ip_addresses.29_searching_with_spatial_indexes

    • 1
  3. Evan Carroll
    2017-11-10T07:33:16+08:002017-11-10T07:33:16+08:00

    ip4r

    Primeiro, construa adicione a extensão (melhores instruções) no Github.

    CREATE EXTENSION ip4r;
    

    Vamos começar com quase a mesma coisa que você tinha antes, crie os tipos de ip como ip4alternativa. Não faça nada a PRIMARY KEYe não adicione índices nos tipos. Vamos mudar a tabela após o carregamento.

    CREATE TABLE ip2location_db11
    (
      ip_from ip4 NOT NULL,   -- First IP address in netblock.
      ip_to   ip4 NOT NULL, -- Last IP address in netblock.
      ....
    );
    \copy ip2location_db11 FROM 'IP2LOCATION-LITE-DB11.CSV' WITH CSV QUOTE AS '"';
    

    Agora vamos atualizá-los para umip4r

    BEGIN;
      ALTER TABLE ip2location_db11
        ADD iploc_range ip4r;
      UPDATE ip2location_db11
        SET iploc_range = ip4r(ip_from,ip_to);
      ALTER TABLE ip2location_db11
        DROP COLUMN ip_from,
        DROP COLUMN ip_to;
    COMMIT;
    

    Agora vamos indexá-lo

    CREATE INDEX ON ip2location_db11
       USING gist (iploc_range);
    VACUUM ANALYZE ip2location_db11;
    

    E questione sobre isso,

    SELECT *
    FROM ip2location_db11
    WHERE iploc_range >>= '1.2.3.4';
    
    • 1

relate perguntas

  • Sequências Biológicas do UniProt no PostgreSQL

  • Como determinar se um Índice é necessário ou necessário

  • Onde posso encontrar o log lento do mysql?

  • Como posso otimizar um mysqldump de um banco de dados grande?

  • 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