0

我有一个包含以下类型数据的表:

create table store (
    n_id             serial not null primary key,
    n_place_id       integer not null references place(n_id),
    dt_modified      timestamp not null,
    t_tag            varchar(4),
    n_status         integer not null default 0
    ...
    (about 50 more fields)
);

在下面的查询中使用的 n_id、n_place_id、dt_modified 和所有其他字段都有索引。

该表目前包含大约 100,000 行,但可能会增长到接近一百万甚至更多。然而,现在让我们假设我们保持在 100K 左右。

我正在尝试从这些表中选择满足两个条件的行:

  1. n_place_id位于特定子集中的所有行(这部分很简单);或者
  2. 对于所有其他n_place_id值,前十行排序依据dt_modified(这是变得更加复杂的地方)。

在一个 SQL 中做这件事似乎太痛苦了,所以我很高兴有一个存储函数。我的函数是这样定义的:

create or replace function api2.fn_api_mobile_objects()
  returns setof store as
$body$
declare
    maxres_free integer := 10;
    resulter    store%rowtype;
    mcnt        integer := 0;
    previd      integer := 0;
begin
    create temporary table paid on commit drop as
    select n_place_id from payments where t_reference is not null and now()::date between dt_paid and dt_valid;

    for resulter in
        select * from store where n_status > 0 and t_tag is not null order by n_place_id, dt_modified desc
    loop
        if resulter.n_place_id in (select n_place_id from paid) then
            return next resulter;
        else
            if previd <> resulter.n_place_id then
                mcnt := 0;
                previd := resulter.n_place_id;
            end if;

            if mcnt < maxres_free then
                return next resulter;
                mcnt := mcnt + 1;
            end if;
        end if;
    end loop;
end;$body$
  language 'plpgsql' volatile;

问题是

select * from api2.fn_api_mobile_objects()

执行大约需要 6-7 秒。考虑到之后需要将此结果集join编辑到其他 3 个表,并应用一堆附加条件并应用进一步排序,这显然是不可接受的。

好吧,我仍然需要获取这些数据,所以要么我在函数中遗漏了一些东西,要么我需要重新考虑整个算法。不管怎样,我需要这方面的帮助。

4

3 回答 3

1
CREATE TABLE store
    ( n_id             serial not null primary key
    , n_place_id       integer not null -- references place(n_id)
    , dt_modified      timestamp not null
    , t_tag            varchar(4)
    , n_status         integer not null default 0
        );
INSERT INTO store(n_place_id,dt_modified,n_status)
SELECT n,d,n%4
FROM generate_series(1,100) n
, generate_series('2012-01-01'::date ,'2012-10-01'::date, '1 day'::interval ) d
        ;

WITH zzz AS (
        SELECT n_id AS n_id
        , rank() OVER (partition BY n_place_id ORDER BY dt_modified) AS rnk
        FROM store
        )
SELECT st.*
FROM store st
JOIN zzz ON zzz.n_id = st.n_id
WHERE st.n_place_id IN ( 1,22,333)
OR zzz.rnk <=10
        ;

更新:这里是与子查询相同的自连接结构(CTE 被规划器处理得有点不同):

SELECT st.*
FROM store st
JOIN ( SELECT sx.n_id AS n_id
        , rank() OVER (partition BY sx.n_place_id ORDER BY sx.dt_modified) AS zrnk
        FROM store sx
        ) xxx ON xxx.n_id = st.n_id
WHERE st.n_place_id IN ( 1,22,333)
OR xxx.zrnk <=10
        ;
于 2012-10-03T10:11:23.117 回答
1

经过一番努力,我设法让存储的函数在 1 秒多的时间内返回结果(这是一个巨大的改进)。现在函数看起来像这样(我添加了附加条件,对性能影响不大):

create or replace function api2.fn_api_mobile_objects(t_search varchar)
  returns setof store as
$body$
declare
    maxres_free integer := 10;
    resulter    store%rowtype;
    mid     integer := 0;
begin
    create temporary table paid on commit drop as
    select n_place_id from payments where t_reference is not null and now()::date between dt_paid and dt_valid
    union
    select n_place_id from store where n_status > 0 and t_tag is not null group by n_place_id having count(1) <= 10;

    for resulter in
        select * from store
        where n_status > 0 and t_tag is not null
        and (t_name ~* t_search or t_description ~* t_search)
        and n_place_id in (select n_place_id from paid)
    loop
        return next resulter;
    end loop;

    for mid in
        select distinct n_place_id from store where n_place_id not in (select n_place_id from paid)
    loop
        for resulter in
            select * from store where n_status > 0 and t_tag is not null and n_place_id = mid order by dt_modified desc limit maxres_free
        loop
            return next resulter;
        end loop;
    end loop;

end;$body$
  language 'plpgsql' volatile;

这在我的本地机器上运行只需 1 秒多一点,而在现场运行大约需要 0.8-1.0 秒。就我的目的而言,这已经足够了,尽管我不确定随着数据量的增长会发生什么。

于 2012-10-03T10:51:33.613 回答
0

作为一个简单的建议,我喜欢进行这种故障排除的方式是构建一个查询,让我大部分时间到达那里,并适当地优化它,然后在它周围添加必要的 pl/pgsql 东西。这种方法的主要优点是您可以根据查询计划进行优化。

此外,如果您不处理很多行,array_agg() 和 unnest() 是您的朋友,因为它们允许您(在 Pg 8.4 及更高版本上!)省去临时表管理开销并简单地构造和查询数组内存中的元组作为关系。如果您只是在内存中访问数组而不是临时表,它也可能会执行得更好(更少的计划开销和更少的查询开销)。

同样在您更新的查询中,我会考虑用子查询或连接替换最终循环,从而允许规划器决定何时进行嵌套循环查找或何时尝试找到更好的方法。

于 2012-10-04T07:32:00.237 回答