优化postgres视图以获取时间戳和来自另一个表的字段聚合

时间:2014-03-29 23:33:07

标签: postgresql query-optimization aggregate-functions

我已经大大简化了这些示例,希望能够提出一个可以回答的明确问题:

考虑一个事件表

CREATE TABLE alertable_events
(
  unique_id text NOT NULL DEFAULT ''::text,
  generated_on timestamp without time zone NOT NULL DEFAULT now(),
  message_text text NOT NULL DEFAULT ''::text,
  CONSTRAINT pk_alertable_events PRIMARY KEY (unique_id),
)

包含以下数据:

COPY alertable_events (unique_id,message_text,generated_on) FROM stdin;
one message one 2014-03-20 06:00:00.000000
two message two 2014-03-21 06:00:00.000000
three   message three   2014-03-22 06:00:00.000000
four    message four    2014-03-23 06:00:00.000000
five    message five    2014-03-24 06:00:00.000000
\.

对于每个事件,都有一个字段列表

CREATE TABLE alertable_event_fields
(
  unique_id text NOT NULL DEFAULT ''::text,
  field_name text NOT NULL,
  field_value text NOT NULL DEFAULT ''::text,
  CONSTRAINT pk_alertable_event_fields PRIMARY KEY (unique_id, field_name),
  CONSTRAINT fk_alertable_event_fields_0 FOREIGN KEY (unique_id)
      REFERENCES alertable_events (unique_id) MATCH SIMPLE
      ON UPDATE CASCADE ON DELETE CASCADE,
)

包含以下数据:

COPY alertable_event_fields (unique_id,field_name,field_value) FROM stdin;
one field1  a
one field2  b
two field1  z
two field2  y
three   field1  a
three   field2  m
four    field1  a
four    field2  b
five    field1  z
five    field2  y
\.

我想定义一个产生以下内容的视图:

| unique_id | fields | message_text  | generated_on               | updated_on                 | count |
| five      | z|y    | message five  | 2014-03-21 06:00:00.000000 | 2014-03-24 06:00:00.000000 | 2     |
| four      | a|b    | message four  | 2014-03-20 06:00:00.000000 | 2014-03-23 06:00:00.000000 | 2     |
| three     | a|m    | message three | 2014-03-22 06:00:00.000000 | 2014-03-22 06:00:00.000000 | 1     |

值得注意的是:

  1. fields是字段值的管道分隔字符串(或任何序列化)(field_name:field_value对的json编码会更好......但我现在可以使用pipe_delim)
  2. 输出按匹配字段分组。 更新3/30 12:45上午这些值按字母顺序按其字段名排序,因此a|bb|a不匹配
  3. 生成与该字段集匹配的事件的计数。 更新3/30 12:45 am 每个unique_id可以有不同数量的字段,匹配需要匹配所有字段而不是字段的子集。
  4. generated_on是第一个事件的时间戳
  5. updated_on是最近事件的时间戳
  6. message_text是最近事件的message_text
  7. 我已经制作了这个视图,它适用于小型数据集,但是,随着alertable_events表的增长,它变得异常缓慢。我只能假设我在视图中做错了事,因为我从未处理过任何非常丑陋的事情。

    更新3/30美国东部时间下午12:15 看起来我可能遇到服务器调优问题导致运行时间过长,请参阅已添加explain以获取更多信息。如果你在那里看到一个明显的问题,我对调整服务器的配置非常感兴趣。

    任何人都可以将一个能够很好地处理大型数据集并且运行时间明显好于此的视图拼凑在一起吗?也许使用hstore? (我最好运行9.2,如果我可以对这些字段进行良好的json编码,则为9.3。)

    更新3/30 11:30 AM 我开始认为我的问题可能是服务器调整(这意味着我需要与SA交谈)这里'一个非常简单的explain (analyze,buffers),它显示了一个荒谬的运行时间,在unduplicated_event_fields中只有8k行

    更新3/30 7:20 PM 我使用SET WORK_MEM='5MB'将可用内存提高到5MB(这对于下面的查询来说很多),奇怪的是,即使计划程序进入内存快速排序,它实际上平均延长了100毫秒!

    explain (analyze,buffers) 
    SELECT a.unique_id,
           array_to_string(array_agg(a.field_value order by a.field_name),'|') AS "values"
    FROM alertable_event_fields a
    GROUP BY a.unique_id;
                                                                  QUERY PLAN                                                               
    ---------------------------------------------------------------------------------------------------------------------------------------
     GroupAggregate  (cost=771.11..892.79 rows=4056 width=80) (actual time=588.679..630.989 rows=4056 loops=1)
       Buffers: shared hit=143, temp read=90 written=90
       ->  Sort  (cost=771.11..791.39 rows=8112 width=80) (actual time=588.591..592.622 rows=8112 loops=1)
             Sort Key: unique_id
             Sort Method: external merge  Disk: 712kB
             Buffers: shared hit=143, temp read=90 written=90
             ->  Seq Scan on alertable_event_fields a  (cost=0.00..244.40 rows=8112 width=80) (actual time=0.018..5.478 rows=8112 loops=1)
                   Filter: (message_name = 'LIMIT_STATUS'::text)
                   Buffers: shared hit=143
     Total runtime: 632.323 ms
    (10 rows)
    

    更新3/30美国东部时间上午4:10 我仍然不完全满意,并且对任何进一步优化感兴趣。我需要支持500毫秒/秒的稳定状态,虽然大多数情况不应该是"事件",但是当我进行压力测试时,我会得到一点积压。

    更新3/30 12:00 PM EDT 这是我最可读的迭代,不幸的是,对于4000行我仍然看着600ms的运行时间! ...(见上文,因为它主要包含在最内部的查询中)这里的任何帮助都将非常感激

    CREATE OR REPLACE VIEW views.unduplicated_events AS 
     SELECT a.unique_id,a.message_text,
            b."values",b.generated_on,b.updated_on,b.count
     FROM alertable_events a
     JOIN (
           SELECT b."values", 
                  min(a.generated_on) AS generated_on,
                  max(a.generated_on) AS updated_on,
                  count(*) AS count
           FROM alertable_events a
           JOIN ( 
                 SELECT a.unique_id,
                        array_to_string(array_agg(a.field_value order by a.field_name),'|') AS "values"
                 FROM alertable_event_fields a
                 GROUP BY a.unique_id
                ) b USING (unique_id)
           GROUP BY b."values"
     ) b ON a.generated_on=b.updated_on
     ORDER BY updated_on DESC;
    

    更新3/30 12:00 PM EDT 删除了旧东西,因为这太长了

1 个答案:

答案 0 :(得分:1)

一些指针

查询无效

您当前的查询不正确,除非generated_on是唯一的,而问题中未声明,可能不是这样:

CREATE OR REPLACE VIEW views.unduplicated_events AS 
SELECT ...
FROM alertable_events a
JOIN (   ...  ) b ON a.generated_on=b.updated_on  -- !! unreliable

可能更快

SELECT DISTINCT ON (f.fields) 
       unique_id                   -- most recent
     , f.fields
     , e.message_text              -- most recent
     , min(e.generated_on) OVER (PARTITION BY f.fields) AS generated_on -- "first"
     , e.generated_on                                   AS updated_on   -- most recent
     , count(*)            OVER (PARTITION BY f.fields) AS ct
FROM   alertable_events e
JOIN  (
   SELECT unique_id, array_to_string(array_agg(field_value), '|') AS fields
   FROM  (
      SELECT unique_id, field_value
      FROM   alertable_event_fields
      ORDER  BY 1, field_name   -- a bit of a hack, but much faster
      ) f
   GROUP  BY 1
   ) f USING (unique_id)
ORDER  BY f.fields, e.generated_on DESC;

SQL Fiddle.

结果目前按fields排序。如果您需要不同的排序顺序,则需要将其包装在另一个子查询中...

重点

  • 更新:输出列名generated_on与输入列generated_on冲突。您必须对列e.generated_on进行表限定才能引用输入列。我在任何地方都添加了表限定以明确它,但实际上只需要ORDER BY子句。 Per documentation:

      

    如果ORDER BY表达式是一个与输出匹配的简单名称   列名和输入列名称ORDER BY将其解释为   输出列名称。这与GROUP的选择相反   BY将在同样的情况下。这种不一致就是这样   与SQL标准兼容。

    我一直在回答几个问题。现在它也得到了我,抱歉 更新的查询也应该更快(一直如此)。再次运行EXPLAIN ANALYZE

  • 对于整个查询,索引几乎无法使用。仅当您选择特定行时...一个可能的例外:alertable_event_fields的覆盖索引:

    CREATE INDEX f_idx1
    ON alertable_event_fields (unique_id, field_name, field_value);
    

    Lots of write operations might void the benefit, though.

  • array_agg(field_value ORDER BY ...)对于大集合而言往往比在子查询中预排序慢。

  • DISTINCT ON在这里很方便,但不确定它是否真的更快,因为ctgenerated_on必须在单独的窗口函数中计算,这需要另一个排序步骤。

  • work_mem:将设置为高可能会严重损害性能。 More in the Postgres Wiki."Craig's list"

通常,难以优化。索引失败,因为排序顺序取决于两个表。如果您可以使用快照,请考虑MATERIALIZED VIEW