从大 table 获取每个时间戳的最新记录 - 未使用索引
Get latest records per timestamp from large table - Index is not used
我有几个暂存 tables,其中记录定期 inserted/updated(未删除)。
每个 table 都有一个 'BEFORE UPDATE' 触发器使用当前时间戳更新时间戳列。
有一个进程 运行 会根据存储在控件 table 中的时间戳定期从每个阶段 table 获取最新记录(增量)。这是使用实体化视图完成的。
每次上述过程运行s
时,控件table都会更新为从实体化视图中找到的max(timestamp)
对照table:
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' 列上的索引 table
查询 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 > [=16=])
Rows Removed by Filter: 4614988
Buffers: shared hit=173254
InitPlan 1 (returns [=16=])
-> 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 关闭)
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
不用说我 运行 分析分期 table 并且我已经相应地设置 autovacuum/autoanalyze
那么计划者在暂存 table 上使用 '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 = ;
$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))
我是 expecting/hoping 值(时间戳)将在执行主查询之前计算并可供优化器使用。
如果有人指出上述情况下优化器的内部行为是什么,那就太好了!
优化器非常清楚 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会选择索引扫描。
解决方案
按照@Laurenz 的建议,我尝试将两个查询分开,并将第一个查询的结果用作第二个查询的参数。
我使用 'plpgsql' 函数做到了 returns a table
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 倍的查询速度,一点也不差:)
当然,如果您可以提出更好的答案,请随时提出!
我有几个暂存 tables,其中记录定期 inserted/updated(未删除)。
每个 table 都有一个 'BEFORE UPDATE' 触发器使用当前时间戳更新时间戳列。
有一个进程 运行 会根据存储在控件 table 中的时间戳定期从每个阶段 table 获取最新记录(增量)。这是使用实体化视图完成的。
每次上述过程运行s
时,控件table都会更新为从实体化视图中找到的max(timestamp)对照table:
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' 列上的索引 table
查询 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 > [=16=])
Rows Removed by Filter: 4614988
Buffers: shared hit=173254
InitPlan 1 (returns [=16=])
-> 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 关闭)
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
不用说我 运行 分析分期 table 并且我已经相应地设置 autovacuum/autoanalyze
那么计划者在暂存 table 上使用 '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 = ;
$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))
我是 expecting/hoping 值(时间戳)将在执行主查询之前计算并可供优化器使用。
如果有人指出上述情况下优化器的内部行为是什么,那就太好了!
优化器非常清楚 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会选择索引扫描。
解决方案
按照@Laurenz 的建议,我尝试将两个查询分开,并将第一个查询的结果用作第二个查询的参数。
我使用 'plpgsql' 函数做到了 returns a table
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 倍的查询速度,一点也不差:)
当然,如果您可以提出更好的答案,请随时提出!