我有以下存储过程来生成动态查询。
根据条件/过滤器列表,它会找到属于给定Visitors
的所有App
。 app_id
作为参数传入。
如果我使用app id调用该函数,并在动态查询中使用此参数,它将在大约200ms内运行。
但是,如果我对app_id
进行硬编码,则会在<为20ms。
这是我如何调用过程
的示例SELECT id
FROM find_matching_visitors('my_app_id', '{}', '{( field = ''app_name'' and string_value ILIKE ''My awesome app'' )}')
任何想法都是为什么?
CREATE OR REPLACE FUNCTION find_matching_visitors(app_id text, default_filters text[], custom_filters text[])
RETURNS TABLE (
id varchar
) AS
$body$
DECLARE
default_filterstring text;
custom_filterstring text;
default_filter_length integer;
custom_filter_length integer;
sql VARCHAR;
BEGIN
default_filter_length := COALESCE(array_length(default_filters, 1), 0);
custom_filter_length := COALESCE(array_length(custom_filters, 1), 0);
default_filterstring := array_to_string(default_filters, ' AND ');
custom_filterstring := array_to_string(custom_filters, ' OR ');
IF custom_filterstring = '' or custom_filterstring is null THEN
custom_filterstring := '1=1';
END IF;
IF default_filterstring = '' or default_filterstring is null THEN
default_filterstring := '1=1';
END IF;
sql := format('
SELECT v.id FROM visitors v
LEFT JOIN trackings t on v.id = t.visitor_id
WHERE v.app_id = ''HARDCODED_APP_ID'' and (%s) and (%s)
group by v.id
', custom_filterstring, default_filterstring, custom_filter_length, custom_filter_length);
RETURN QUERY EXECUTE sql;
END;
$body$
LANGUAGE 'plpgsql';
分析没有硬编码app_id
Limit (cost=481.86..481.99 rows=50 width=531) (actual time=163.579..163.581 rows=9 loops=1)
2 -> Sort (cost=481.86..484.26 rows=960 width=531) (actual time=163.578..163.579 rows=9 loops=1)
3 Sort Key: v0.last_seen DESC
4 Sort Method: quicksort Memory: 30kB
5 -> WindowAgg (cost=414.62..449.97 rows=960 width=531) (actual time=163.553..163.560 rows=9 loops=1)
6 -> Hash Join (cost=414.62..437.97 rows=960 width=523) (actual time=163.525..163.537 rows=9 loops=1)
7 Hash Cond: ((find_matching_visitors.id)::text = (v0.id)::text)
8 -> Function Scan on find_matching_visitors (cost=0.25..10.25 rows=1000 width=32) (actual time=153.918..153.918 rows=9 loops=1)
9 -> Hash (cost=354.19..354.19 rows=4814 width=523) (actual time=9.578..9.578 rows=4887 loops=1)
10 Buckets: 8192 Batches: 1 Memory Usage: 2145kB
11 -> Seq Scan on visitors v0 (cost=0.00..354.19 rows=4814 width=523) (actual time=0.032..4.993 rows=4887 loops=1)
12 Filter: ((NOT merged) AND (((type)::text = 'user'::text) OR ((type)::text = 'lead'::text)))
13 Rows Removed by Filter: 138
14 Planning time: 1.134 ms
15 Execution time: 163.705 ms
分析何时硬编码app_id
Limit (cost=481.86..481.99 rows=50 width=531) (actual time=25.890..25.893 rows=9 loops=1)
2 -> Sort (cost=481.86..484.26 rows=960 width=531) (actual time=25.888..25.890 rows=9 loops=1)
3 Sort Key: v0.last_seen DESC
4 Sort Method: quicksort Memory: 30kB
5 -> WindowAgg (cost=414.62..449.97 rows=960 width=531) (actual time=25.862..25.870 rows=9 loops=1)
6 -> Hash Join (cost=414.62..437.97 rows=960 width=523) (actual time=25.830..25.841 rows=9 loops=1)
7 Hash Cond: ((find_matching_visitors.id)::text = (v0.id)::text)
8 -> Function Scan on find_matching_visitors (cost=0.25..10.25 rows=1000 width=32) (actual time=15.875..15.876 rows=9 loops=1)
9 -> Hash (cost=354.19..354.19 rows=4814 width=523) (actual time=9.936..9.936 rows=4887 loops=1)
10 Buckets: 8192 Batches: 1 Memory Usage: 2145kB
11 -> Seq Scan on visitors v0 (cost=0.00..354.19 rows=4814 width=523) (actual time=0.013..5.232 rows=4887 loops=1)
12 Filter: ((NOT merged) AND (((type)::text = 'user'::text) OR ((type)::text = 'lead'::text)))
13 Rows Removed by Filter: 138
14 Planning time: 0.772 ms
15 Execution time: 26.006 ms
更新1:为两种情况添加了解释。注意:它们实际上是完全相同的计划,只花费的时间不同
更新2:事实证明我需要将app_id
作为参数传递给格式函数,而不是直接嵌入它。这使查询时间缩短到大约20 / 30ms
答案 0 :(得分:2)
硬编码值对于确定最佳查询计划非常重要。 例如:
select * from some_table where id_person=231
select * from some_table where id_person=10
当90%的some_table有id_person = 231 pg使用全表扫描时,因为这是最快的。 当1%的记录具有id_person = 10时,它使用索引扫描。 所以使用的计划取决于参数的值。
当您使用的不是硬编码值时,例如
select * from some_table where id_person=?
无法确定最佳查询计划,查询可能会变慢。