【问题标题】:Slow SQL transaction getting latest timestamp rows from table in Postgres从 Postgres 中的表中获取最新时间戳行的慢速 SQL 事务
【发布时间】:2011-12-04 03:01:05
【问题描述】:

我在 Postgres 中尝试检索购买量大于销售量的目录产品的最新价格时遇到了问题。此时它是一个相当大的表,超过 200 万行。我有这个出于历史目的。我目前使用的是:

select * from ta_price a
  join (
   select catalogproduct_id, max(timestamp) ts
     from ta_price
    group by catalogproduct_id
       ) b on a.catalogproduct_id = b.catalogproduct_id
          and a.timestamp = b.ts
          AND buy > sell;

catalogproduct_id 是 catalogproduct 表的外键。

在总共 2201760 行中,它选择了 2296 行。总运行时间为 181,792.705 毫秒。

关于如何改进这一点的任何见解?

编辑:

我被所有的答案震惊了!我还想在 Django ORM 的范围内更多地限定这个问题。我正在努力在此表上合并一个复合键(或类似键)(使用 catalogproduct_id 和时间戳)。我有一个主键,它是一个自动递增索引,我想这和没有主键一样好。

编辑 2: 添加@Erwin 建议的部分索引后, CREATE INDEX my_partial_idx ON ta_price (catalogproduct_id, timestamp) WHERE buy > sell;,我正在使用来自 @wildplasser 的查询大约 10-12 秒的查询时间。为了进一步澄清,我的表格是产品价格(买卖)随时间变化的快照。在任何给定时间,我都想知道当前(截至最新快照时间)哪些产品具有buy > sell

【问题讨论】:

  • 查询的初始运行使@wildplasser 占每个人大约一半的时间,同时仍然尊重预期的结果集。由于尝试在 Django 中工作,我仍然没有真正的索引。
  • Django 与否——如果你想要更快的速度,你需要一个或多个合适的索引。但首先要做的事情是:你的 postgres 版本是什么?
  • 是的,我在其中找到了您的部分索引,但使用了@wildplasser 查询。现在缩短到10秒左右!!谢谢!!

标签: sql django postgresql


【解决方案1】:

考虑后修改答案

SELECT *
  FROM ta_price a
  JOIN (
   SELECT catalogproduct_id, max(timestamp) ts
     FROM ta_price
    GROUP BY catalogproduct_id
        ) b ON a.catalogproduct_id = b.catalogproduct_id
           AND a.timestamp = b.ts
           AND a.buy > a.sell;

buysell在您的问题中不合格。根据buy > sell 的选择性,您可以通过将相同的WHERE 子句添加到子选择来加快查询速度。 但是,这会产生不同的结果。我偶然添加了它,您可能忽略了它:

SELECT *
  FROM ta_price a
  JOIN (
   SELECT catalogproduct_id, max(timestamp) ts
     FROM ta_price
    WHERE buy > sell
    GROUP BY catalogproduct_id
        ) b ON a.catalogproduct_id = b.catalogproduct_id
           AND a.timestamp = b.ts
 WHERE a.buy > a.sell;

无论哪种方式,像@Will 这样的简单索引都会有所帮助:
CREATE INDEX my_idx ON ta_price (catalogproduct_id, timestamp);

不过,有一种更好的方法。
子选择中的无条件max() 将导致顺序表扫描,而不管索引如何。 220 万行这样的操作永远不会很快。
JOIN 条件与外部SELECTWHERE 子句相结合,将从上述索引中受益。根据buy > sell 的选择性,partial index 的速度会稍微快一些或显着加快,相应地,在磁盘和 RAM 中会更小:

CREATE INDEX my_partial_idx ON ta_price (catalogproduct_id, timestamp)
 WHERE buy > sell;

在这种情况下,索引中列的顺序无关紧要。它还将加快我的第二个查询变体。

你提到这张桌子是为了“历史”目的?如果这意味着没有新数据,您可以使用物化视图大大加快速度。


附带说明:我不会使用timestamp 作为列名。它在 PostgreSQL 中是允许的,但在所有 SQL 标准中它都是 reserved word


好的,第一件事最后:对于一个 220 万行的表,您需要 比 postgres 开箱即用的资源多得多

  • 首先查看您的 postgresql.conf 文件并检查 shared_bufferswork_mem 的设置。
  • 咨询postgres wiki for performance tuning
  • 请参阅ressource consumption上的精美手册
  • 请参阅planner costs上的精美手册
  • 增加这些统计设置:
    ALTER TABLE tmp.ta_price ALTER COLUMN buy SET STATISTICS 1000;
    ALTER TABLE tmp.ta_price ALTER COLUMN sell SET STATISTICS 1000;
    ALTER TABLE tmp.ta_price ALTER COLUMN ts SET STATISTICS 1000;

    然后运行ANALYZE tmp.ta_price;

  • 确保autovacuum 正在运行。如果有疑问,运行VACUUM ANALYZE ta_price 看看是否有效果。


我在资源有限的 pg 8.4 安装中使用了 wildplasser 的测试设置(非常有帮助!)。 以下是EXPLAIN ANYLYZE总运行时间

欧文 1) 901.487 毫秒 Wildplasser 1) 1148.045 毫秒 AH 2922.113 毫秒

带有附加 (buy > sell) 子句的变体 2:

欧文 2) 536.678 毫秒 Wildplasser 2) 809.215 毫秒

部分索引:

Erwin 1) 1166.793 毫秒——比意外慢(!)

可能计划器成本已关闭,此测试数据库集群针对主数据库进行了优化 有更多的资源。

wildplasser 1) 1122.609 毫秒 - 休息时间比预期的更快 欧文 2) 481.487 毫秒 Wildplasser 2) 769.887 毫秒

简历

A.H. 的版本需要更长的时间(与您报告的结果相同)。窗口函数往往很慢,尤其是在旧版本的 postgres 上。我的替代查询速度是预期的两倍。问题是,如果需要不同的结果 - 也许不是。

无论如何,那是 300k 行。在 5 年历史的服务器上,在 8.4 版上查询需要 0.5 - 1 秒,但资源有限(但主要是正确的设置)。有了一台不错的机器和不错的设置(足够的 RAM!),您至少应该将其降低到 10 秒以下

【讨论】:

  • 在提高性能方面运气不佳。历史方面是需要随时间绘制买入/卖出价格的图表。拍摄快照时,我不断在此表中添加新的价格/时间戳。
  • @bdilly:这很奇怪。我实际上在 posgtres 9.0 上使用 5000 行的中等大小样本对此进行了测试。部分索引肯定会加快速度。如果您可以更详细地描述您想要准确检索的内容(买 > 卖?)并添加更多数字,那么这里可能会出现更多想法。 (为此编辑问题!)
  • 我昨天进行了大约 4M 行的测试。正如预期的那样,运行时间是几秒钟。 [回家后我会添加结果] 如果内部循环中的预期匹配数变小,查询计划可能会转移到 hashmap。窗口函数的性能相对较差,因为它不会被拉出子查询(也取决于正在使用的聚合;语义上很难为优化器决定)。 9.1 中可能已更改。
【解决方案2】:
SELECT * from ta_price a
WHERE NOT EXISTS (
  SELECT *
  FROM ta_price b
  WHERE b.catalogproduct_id = a.catalogproduct_id
    AND b.timestamp > a.timestamp
    -- AND b.buy > b.sell -- Not clear if OP wants this
    )
AND a.buy > a.sell
;

(catalogproduct_id,timestamp)上的索引可能是有益的。 可能需要子查询中的额外条件“AND b.buy > b.sell”(OP 中的文本不清楚实际需要什么)。

更新:“时间戳”是保留字。我改变了一点。还添加了测试数据。

DROP SCHEMA tmp cascade;
CREATE SCHEMA tmp ;

CREATE TABLE tmp.ta_price
    ( catalogproduct_id INTEGER NOT NULL
    , tttimestamp timestamp NOT NULL
    , buy DECIMAL (10,2)
    , sell DECIMAL (10,2)
    );
INSERT INTO tmp.ta_price(catalogproduct_id,tttimestamp,buy,sell)
    SELECT serie_n
    , serie_t
    , serie_v + ((100* random()) - 30)
    , serie_v + ((100* random()) - 20)
    FROM generate_series (1,10000) serie_n
    , generate_series ( '2011-09-01 00:00:00' , '2011-10-01 00:00:00' , '1 day' ::interval) serie_t
    , generate_series ( 100 , 100 ) serie_v
    ;
DELETE FROM tmp.ta_price WHERE random() < 0.02;

CREATE INDEX tmptmp ON tmp.ta_price (catalogproduct_id,tttimestamp);

-- there may be some duplicate records: clear them
DELETE FROM tmp.ta_price a
WHERE EXISTS (SELECT * FROM tmp.ta_price b
    WHERE b.catalogproduct_id = a.catalogproduct_id
    AND b.tttimestamp = a.tttimestamp
    AND b.ctid > a.ctid
    );

DROP INDEX tmp.tmptmp ;

ALTER TABLE tmp.ta_price
    ADD PRIMARY KEY (catalogproduct_id,tttimestamp)
    ;

EXPLAIN ANALYZE
SELECT * from tmp.ta_price a
WHERE NOT EXISTS (
  SELECT *
  FROM tmp.ta_price b
  WHERE b.catalogproduct_id = a.catalogproduct_id
    AND b.tttimestamp > a.tttimestamp
    -- AND b.buy > b.sell -- Not clear if OP wants this
    )
AND a.buy > a.sell
;

查询计划:(对于 ta_price 中的 300K 记录)

  ------------------------------------------------------------------------------
 Nested Loop Anti Join  (cost=0.00..8607.82 rows=67508 width=38) (actual time=457.486..482.943 rows=4052 loops=1)
   ->  Seq Scan on ta_price a  (cost=0.00..6381.34 rows=101262 width=38) (actual time=0.027..80.256 rows=123142 loops=1)
         Filter: (buy > sell)
   ->  Index Scan using ta_price_pkey on ta_price b  (cost=0.00..10.57 rows=506 width=12) (actual time=0.003..0.003 rows=1 loops=123142)
         Index Cond: ((b.catalogproduct_id = a.catalogproduct_id) AND (b.tttimestamp > a.tttimestamp))
 Total runtime: 483.325 ms
(6 rows)

【讨论】:

    【解决方案3】:

    您的查询会将表连接到自身以获得最大值。您可以尝试“Window Functions”来防止这种情况发生 - 也许它们在您的情况下效果更好:

    SELECT * FROM (
        SELECT *, rank() OVER w
        FROM ta_price                             
        WINDOW w AS (PARTITION BY catalogproduct_id ORDER BY timestamp DESC)
    ) c WHERE c.rank = 1 AND c.buy > c.sell;
    

    【讨论】:

    • 我一开始使用这种方法,虽然结果正确,但比我目前的方法慢。
    【解决方案4】:

    我会尝试在 catalogproduct_id 和时间戳上添加索引。对我来说,它似乎是在扫描表格,没有其他信息。

    【讨论】:

      猜你喜欢
      • 2014-10-12
      • 2013-01-17
      • 2023-03-19
      • 1970-01-01
      • 1970-01-01
      • 2021-12-27
      • 1970-01-01
      • 2013-04-27
      • 1970-01-01
      相关资源
      最近更新 更多