sql - 硬编码函数参数产生 5 倍的加速

标签 sql postgresql

我有以下存储过程来生成动态查询。

给定条件/过滤器列表,它会找到属于给定 App 的所有 Visitorsapp_id 作为参数传入。

如果我使用应用程序 ID 调用该函数,并在动态查询中使用此参数,它会在大约 200 毫秒内运行。

但是,如果我对 app_id 进行硬编码,它会在 < 20 毫秒内运行。

这里是我如何调用过程的例子

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/30 毫秒

最佳答案

硬编码值对于确定最佳查询计划很重要。 例如:

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=?

它无法确定最佳查询计划,查询速度可能会变慢。

关于sql - 硬编码函数参数产生 5 倍的加速,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40526608/

相关文章:

postgresql - 从函数返回结果集

SQL:在行或其他表中定义的日期范围之间求和销售额

sql - 在 SQL 中将多行合并为一行

sql - psql max group by 仅针对某些列

postgresql - postgres中的子查询错误

sql - 创建SQL表时外键约束引用的列 "parent_id"不存在

MySQL GROUP_CONCAT 位于另一个 GROUP_CONCAT 中

sql - 甲骨文 SQL : How to get xml from hierarchical table

mysql - 根据计数结果选择不同的数据

sql - 索引不会提高性能