【问题标题】:Slow select distinct query on postgres在 postgres 上缓慢选择不同的查询
【发布时间】:2011-05-17 14:59:25
【问题描述】:

我在一个基本上收集日志信息的表上非常频繁地执行以下两个查询。两者都从大量行中选择不同的值,但其中的不同值少于 10 个。

我已经分析了页面完成的两个“不同”查询:

marchena=> explain select distinct auditrecor0_.bundle_id as col_0_0_ from audit_records auditrecor0_;
                                          QUERY PLAN                                          
----------------------------------------------------------------------------------------------
 HashAggregate  (cost=1070734.05..1070734.11 rows=6 width=21)
   ->  Seq Scan on audit_records auditrecor0_  (cost=0.00..1023050.24 rows=19073524 width=21)
(2 rows)

marchena=> explain select distinct auditrecor0_.server_name as col_0_0_ from audit_records auditrecor0_;
                                          QUERY PLAN                                          
----------------------------------------------------------------------------------------------
 HashAggregate  (cost=1070735.34..1070735.39 rows=5 width=13)
   ->  Seq Scan on audit_records auditrecor0_  (cost=0.00..1023051.47 rows=19073547 width=13)
(2 rows)

两者都对列进行序列扫描。但是,如果我关闭 enable_seqscan(尽管这个名称仅禁用对具有索引的列进行序列扫描),查询会使用索引,但速度会更慢:

marchena=> set enable_seqscan = off;
SET
marchena=> explain select distinct auditrecor0_.bundle_id as col_0_0_ from audit_records auditrecor0_;
                                                       QUERY PLAN                                                       
------------------------------------------------------------------------------------------------------------------------
 Unique  (cost=0.00..19613740.62 rows=6 width=21)
   ->  Index Scan using audit_bundle_idx on audit_records auditrecor0_  (cost=0.00..19566056.69 rows=19073570 width=21)
(2 rows)

marchena=> explain select distinct auditrecor0_.server_name as col_0_0_ from audit_records auditrecor0_;
                                                       QUERY PLAN                                                       
------------------------------------------------------------------------------------------------------------------------
 Unique  (cost=0.00..45851449.96 rows=5 width=13)
   ->  Index Scan using audit_server_idx on audit_records auditrecor0_  (cost=0.00..45803766.04 rows=19073570 width=13)
(2 rows)

bundle_id 和 server_name 列都有 btree 索引,我应该使用不同类型的索引来快速选择不同的值吗?

【问题讨论】:

    标签: postgresql


    【解决方案1】:
    BEGIN; 
    CREATE TABLE dist ( x INTEGER NOT NULL ); 
    INSERT INTO dist SELECT random()*50 FROM generate_series( 1, 5000000 ); 
    COMMIT;
    CREATE INDEX dist_x ON dist(x);
    
    
    VACUUM ANALYZE dist;
    EXPLAIN ANALYZE SELECT DISTINCT x FROM dist;
    
    HashAggregate  (cost=84624.00..84624.51 rows=51 width=4) (actual time=1840.141..1840.153 rows=51 loops=1)
       ->  Seq Scan on dist  (cost=0.00..72124.00 rows=5000000 width=4) (actual time=0.003..573.819 rows=5000000 loops=1)
     Total runtime: 1848.060 ms
    

    PG 不能(还)使用不同的索引(跳过相同的值),但你可以这样做:

    CREATE OR REPLACE FUNCTION distinct_skip_foo()
    RETURNS SETOF INTEGER
    LANGUAGE plpgsql STABLE 
    AS $$
    DECLARE
        _x  INTEGER;
    BEGIN
        _x := min(x) FROM dist;
        WHILE _x IS NOT NULL LOOP
            RETURN NEXT _x;
            _x := min(x) FROM dist WHERE x > _x;
        END LOOP;
    END;
    $$ ;
    
    EXPLAIN ANALYZE SELECT * FROM distinct_skip_foo();
    Function Scan on distinct_skip_foo  (cost=0.00..260.00 rows=1000 width=4) (actual time=1.629..1.635 rows=51 loops=1)
     Total runtime: 1.652 ms
    

    【讨论】:

    • 此行为是否特定于版本?在 9.0.2 上,我得到了与你相同的执行计划,但我的函数扫描比我的顺序扫描长 15 倍。 ("PostgreSQL 9.0.2, Visual C++ build 1500, 32-bit 编译")
    • 你做了吗:“在 dist(x) 上创建索引 dist_x; VACUUM ANALYZE dist;” ?也尝试 EXPLAIN ANALYZE SELECT min(x) FROM dist WHERE x > somevalue;
    • 啊。我懂了。我在创建函数之前删除了索引。
    【解决方案2】:

    您正在从整个表中选择不同的值,这会自动导致 seq 扫描。你有数百万行,所以它一定会很慢。

    有一个技巧可以更快地获取不同的值,但它仅在数据具有已知(并且相当小)的一组可能值时才有效。例如,我认为您的 bundle_id 引用了某种较小的 bundles 表。这意味着你可以写:

    select bundles.bundle_id
    from bundles
    where exists (
          select 1 from audit_records
          where audit_records.bundle_id = bundles.bundle_id
          );
    

    这应该会导致对 bundle 进行嵌套循环/seq 扫描 -> 使用 bundle_id 上的索引对 audit_records 进行索引扫描。

    【讨论】:

      【解决方案3】:

      我对超过 3 亿条记录的表和具有几个不同值的索引字段有同样的问题。我无法摆脱 seq 扫描,所以我创建了这个函数来模拟使用索引(如果存在)的不同搜索。如果您的表有许多与记录总数成比例的不同值,则此功能不好。它还必须针对多列不同的值进行调整。 警告:此功能对sql注入开放,只能在安全环境中使用。

      解释分析结果:
      使用普通 SELECT DISTINCT 查询:总运行时间:598310.705 毫秒
      使用 SELECT small_distinct(...) 查询:总运行时间:1.156 毫秒

      CREATE OR REPLACE FUNCTION small_distinct(
         tableName varchar, fieldName varchar, sample anyelement = ''::varchar)
         -- Search a few distinct values in a possibly huge table
         -- Parameters: tableName or query expression, fieldName,
         --             sample: any value to specify result type (defaut is varchar)
         -- Author: T.Husson, 2012-09-17, distribute/use freely
         RETURNS TABLE ( result anyelement ) AS
      $BODY$
      BEGIN
         EXECUTE 'SELECT '||fieldName||' FROM '||tableName||' ORDER BY '||fieldName
            ||' LIMIT 1'  INTO result;
         WHILE result IS NOT NULL LOOP
            RETURN NEXT;
            EXECUTE 'SELECT '||fieldName||' FROM '||tableName
               ||' WHERE '||fieldName||' > $1 ORDER BY ' || fieldName || ' LIMIT 1'
               INTO result USING result;
         END LOOP;
      END;
      $BODY$ LANGUAGE plpgsql VOLATILE;
      

      调用示例:

      SELECT small_distinct('observations','id_source',1);
      SELECT small_distinct('(select * from obs where id_obs > 12345) as temp',
         'date_valid','2000-01-01'::timestamp);
      SELECT small_distinct('addresses','state');
      

      【讨论】:

        【解决方案4】:

        在 PostgreSQL 9.3 上,从 Denis 的回答开始:

            select bundles.bundle_id
            from bundles
            where exists (
              select 1 from audit_records
              where audit_records.bundle_id = bundles.bundle_id
              );
        

        只需在子查询中添加一个“限制 1”,我就获得了 60 倍的加速(对于我的用例,有 800 万条记录、一个复合索引和 10k 个组合),从 1800 毫秒到 30 毫秒:

            select bundles.bundle_id
            from bundles
            where exists (
              select 1 from audit_records
              where audit_records.bundle_id = bundles.bundle_id limit 1
              );
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2019-04-15
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-09-06
          相关资源
          最近更新 更多