【问题标题】:Get latest records per timestamp from large table - Index is not used从大表中获取每个时间戳的最新记录 - 不使用索引
【发布时间】:2018-12-08 20:06:47
【问题描述】:

我有几个临时表,其中记录被定期插入/更新(而不是删除)。

每个表都有一个“更新前”触发器,用当前时间戳更新时间戳列。

有一个进程定期运行,根据存储在控制表中的时间戳从每个临时表中获取最新记录(增量)。这是使用物化视图完成的。

每次上述过程运行时,都会使用从物化视图中找到的 max(timestamp) 更新控制表

控制表:

id | staging_table_name | input_last_update_timestamp |
---+--------------------+-----------------------------+
 1 | stg_table1         | 2018-06-29 12:57:19         |
 2 | stg_table2         | 2018-06-29 13:52:19         |

stg_table1

id      | internal_timestamp  
--------+--------------------
6875303 | 2018-06-29 14:18:17 
6874765 | 2018-06-29 14:18:17 
6875095 | 2018-06-29 14:18:17 
6867996 | 2018-06-29 14:18:17 
6873723 | 2018-06-29 14:18:17 
6874594 | 2018-06-29 14:18:17 
6868561 | 2018-06-29 14:18:17 
6875292 | 2018-06-29 14:18:00 
6874595 | 2018-06-29 14:18:00 
6875300 | 2018-06-29 14:18:00 

我尝试了以下查询,但没有一个使用我在暂存表的“internal_timestamp”列上的索引

查询1

SELECT 
    p.id,
    p.internal_timestamp
FROM 
    staging_scm.stg_table1 p,
    control_staging_scm.control_table o
WHERE 
    p.internal_timestamp > o.input_last_update_timestamp 
    AND o.id = 21

查询2

SELECT 
    p.id,
    p.internal_timestamp
FROM 
    staging_scm.stg_table1 p
JOIN 
    control_staging_scm.control_table o ON p.internal_timestamp > o.input_last_update_timestamp
WHERE 
    o.id = 21

查询3

SELECT 
    p.id,
    p.internal_timestamp
FROM 
    staging_scm.stg_table1 p
WHERE 
    p.internal_timestamp > (SELECT o.input_last_update_timestamp 
                            FROM control_staging_scm.control_table o 
                            WHERE o.id = 21)

解释计划:

Query 1 and 2
Nested Loop  (cost=0.03..203273.39 rows=1539352 width=12) (actual time=2013.969..2058.475 rows=520 loops=1)
  Join Filter: (p.internal_timestamp > o.input_last_update_timestamp)
  Rows Removed by Join Filter: 4615088
  Buffers: shared hit=173254
  ->  Index Scan using control_table_pkey on control_table o  (cost=0.03..4.03 rows=1 width=8) (actual time=0.011..0.014 rows=1 loops=1)
        Index Cond: (id = 21)
        Buffers: shared hit=2
  ->  Seq Scan on stg_table1 p  (cost=0.00..187106.17 rows=4618055 width=12) (actual time=0.003..419.628 rows=4615608 loops=1)
        Buffers: shared hit=173252
Planning time: 0.110 ms
Execution time: 2058.533 ms

Query 3

Seq Scan on stg_table1 p  (cost=4.03..189419.23 rows=1539352 width=12) (actual time=2020.801..2054.617 rows=675 loops=1)
  Filter: (internal_timestamp > $0)
  Rows Removed by Filter: 4614988
  Buffers: shared hit=173254
  InitPlan 1 (returns $0)
    ->  Index Scan using control_table_pkey on control_table o  (cost=0.03..4.03 rows=1 width=8) (actual time=0.013..0.014 rows=1 loops=1)
          Index Cond: (id = 21)
          Buffers: shared hit=2
Planning time: 0.155 ms
Execution time: 2054.694 ms

当我设置 enable_seqscan = OFF 时,使用索引并且性能好几个数量级

说明计划(Seqscan OFF)

    Nested Loop  (cost=41794.55..225088.07 rows=1539618 width=12) (actual time=0.100..0.557 rows=407 loops=1)
  Buffers: shared hit=97
  ->  Index Scan using control_table_pkey on control_table o  (cost=0.03..4.03 rows=1 width=8) (actual time=0.010..0.011 rows=1 loops=1)
        Index Cond: (id = 21)
        Buffers: shared hit=2
  ->  Bitmap Heap Scan on stg_table1 p  (cost=41794.52..220465.18 rows=1539618 width=12) (actual time=0.085..0.317 rows=407 loops=1)
        Recheck Cond: (internal_timestamp > o.input_last_update_timestamp)
        Heap Blocks: exact=90
        Buffers: shared hit=95
        ->  Bitmap Index Scan on stg_table1_internal_timestamp_idx  (cost=0.00..41717.54 rows=1539618 width=0) (actual time=0.070..0.070 rows=407 loops=1)
              Index Cond: (internal_timestamp > o.input_last_update_timestamp)
              Buffers: shared hit=5
Planning time: 0.131 ms
Execution time: 0.631 ms

不用说我在临时表上运行了分析,并且我已经相应地设置了 autovacuum/autoanalyze

那么,计划者在 staging 表上使用“internal_timestamp”上的索引需要什么?

更新 1

在尝试@Laurenz 下面的建议之前,我很好奇 CTE 或标量函数在哪里可以解决问题。

但不幸的是,优化器在两种解决方案中都没有使用索引

CTE

WITH x AS (
    SELECT o.input_last_update_timestamp 
    FROM control_staging_scm.control_table o 
    WHERE o.id = 21
)
SELECT 
    p.id,
    p.internal_timestamp
FROM 
    staging_scm.stg_table1 p
WHERE 
    p.internal_timestamp > (SELECT x.input_last_update_timestamp FROM x)

标量函数

CREATE OR REPLACE FUNCTION control_staging_scm.last_update_timestamp(_table_id integer)
RETURNS timestamp without time zone
AS $function$

   SELECT o.input_last_update_timestamp FROM control_staging_scm.control_table o WHERE o.id = $1;

$function$ LANGUAGE 'sql';


SELECT 
    p.id,
    p.internal_timestamp
FROM 
    staging_scm.stg_table1 p
WHERE 
    p.internal_timestamp > (SELECT control_staging_scm.last_update_timestamp(21))

我期待/希望在执行主查询之前计算值(时间戳)并可供优化器使用。

如果有人指出优化器在上述情况下的内部行为,那就太好了!

【问题讨论】:

  • 优化器无法知道在control_table 中找到的一行将导致条件具有足够的选择性以保证使用索引。我现在想不出什么好的补救办法。这是一个粗略的 hack,但添加 LIMIT 子句会有所作为。
  • @LaurenzAlbe 我无法理解优化器不会像预期的那样评估简单的场景。特别是第三个查询与从另一个表中选择 id 的简单子查询有什么区别?在这种情况下,优化器不会先评估子查询条件并将结果传递给外部查询吗?我设置了一个 LIMIT 只是为了看看它是否会有所作为,但它仍然更喜欢顺序扫描。我很好奇在临时表上添加控制表 id 的引用是否会对连接条件产生任何影响。

标签: sql postgresql performance indexing postgresql-9.4


【解决方案1】:

解决方案

正如@Laurenz 所建议的,我尝试将两个查询分开,并将第一个查询的结果用作第二个查询的参数。

我使用返回表的 'plpgsql' 函数完成此操作

CREATE OR REPLACE FUNCTION control_staging_scm.update_stgtable1_delta_mat_view()
 RETURNS TABLE (
    trade_id int4
 ,  internal_timestamp timestamp
 )  
AS $function$

DECLARE 

last_update_timestamp_temp_var timestamp WITHOUT time ZONE;

BEGIN

    SELECT input_last_update_timestamp into last_update_timestamp_temp_var
    FROM control_staging_scm.control_table
    WHERE id=21;

    RETURN QUERY
    SELECT  p.trade_id AS tr_id,          
            p.internal_timestamp AS intr_timestamp,
    FROM staging_scm.stg_table1 p
    WHERE p.internal_timestamp > last_update_timestamp_temp_var;

END;
$function$ LANGUAGE plpgsql;


SELECT * FROM  control_staging_scm.update_stgtable1_delta_mat_view()

说明计划

Function Scan on update_stgtable1_delta_mat_view  (cost=0.05..3.05 rows=1000 width=640) (actual time=0.828..0.847 rows=321 loops=1)
Planning time: 0.049 ms
Execution time: 0.888 ms

最后优化器选择使用索引(参见上面问题的 Seqscan OFF 查询计划)。 所以我们最终得到了一个快约 2000 倍的查询,一点也不差:)

当然,如果您可以提出更好的答案,请随时这样做!

【讨论】:

    【解决方案2】:

    优化器很清楚control_table中只有一个匹配行,但它无法预测input_last_update_timestamp列将具有什么值(仅在查询执行时才知道),所以它没有好处知道应该从stg_table1 获得多少结果行的方法。

    由于缺乏这方面的知识,它回退到估计将选择三分之一的行,这最好通过顺序扫描来完成。

    您可以通过将查询分成两部分来改进:

    SELECT o.input_last_update_timestamp 
    FROM control_staging_scm.control_table o 
    WHERE o.id = 21;
    
    SELECT p.id, p.internal_timestamp
    FROM staging_scm.stg_table1 p
    WHERE p.internal_timestamp > <result from first query>;
    

    那么在计划第二次查询时就会知道实际值,如果只有几行符合条件,PostgreSQL就会选择索引扫描。

    【讨论】:

      猜你喜欢
      • 2014-04-07
      • 1970-01-01
      • 2017-02-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-12-30
      • 2012-11-10
      • 1970-01-01
      相关资源
      最近更新 更多