慢速SQL事务从Postgres中的表获取最新的时间戳行

时间:2011-10-09 21:00:51

标签: sql django postgresql

我在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 ms。

有关如何改善这一点的任何见解?

修改

我被所有答案都震惊了!我想在Django ORM的领域更多地限定这个问题。我正在努力在这个表上合并一个复合键(或类似的东西)(使用catalogproduct_id和timestamp)。我有一个主键,它是一个自动增量索引,我觉得它根本就没有。

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

4 个答案:

答案 0 :(得分:2)

经过一些考虑后修改了答案

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在您的问题中不合格。根据{{​​1}}的选择性,您可以通过向子选择添加相同的buy > sell - 子句来加快查询速度。 但是,这会产生不同的结果。我添加它的机会,你可能忽略了它:

WHERE

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

但是,有一种更好的方法 无论索引如何,子选择中的无条件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; 都将导致顺序表扫描。 2.2m行的这种操作永远不会很快 max()条件与外部JOIN的{​​{1}}子句相结合,将从上面的索引中获益。取决于WHERE SELECT的选择性,partial index的速度会稍微或大幅提高,相应地,在光盘和RAM中会更小:

buy > sell

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

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


旁注:我不会将CREATE INDEX my_partial_idx ON ta_price (catalogproduct_id, timestamp) WHERE buy > sell; 用作列名。它在PostgreSQL中是允许的,但它在所有SQL标准中都是reserved word


好的,第一件事情:对于2.2米行的表格,您需要方式更多资源,而不是postgres开箱即用。

  • 查看postgresql.conf文件并检查timestampshared_buffers的设置是否有效。
  • 咨询postgres wiki for performance tuning
  • 查阅ressource consumption
  • 上的精细手册
  • 查阅planner costs
  • 上的精细手册
  • 增加这些统计设置:
    work_mem

    然后运行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;

  • 确保autovacuum正在运行。如果有疑问,请运行ANALYZE tmp.ta_price;并查看它是否有效。


我在带有限资源的pg 8.4安装上使用了wildplasser的测试设置(非常有用!)。 以下是总运行时间VACUUM ANALYZE ta_price

Erwin 1)        901.487 ms  
wildplasser 1) 1148.045 ms  
A.H.           2922.113 ms  

变体2与附加(buy> sell)条款:

Erwin 2)        536.678 ms  
wildplasser 2)  809.215 ms  

使用部分索引:

Erwin 1)       1166.793 ms  -- slower (!), than unexpected

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

wildplasser 1) 1122.609 ms -- rest is faster as expected  

Erwin 2)        481.487 ms  
wildplasser 2)  769.887 ms  

恢复

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

无论如何,那是300k行。在版本8.4上查询需要0.5 - 1秒,并且在5年历史的服务器上使用有限的资源(但主要是适当的设置)。有了不错的机器和不错的设置(足够的内存!),你应该至少将它降低到 10s以下

答案 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中的文字不清楚实际需要什么)。

更新:“timestamp”是保留字。我改变了一下。还添加了testdata。

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)

答案 2 :(得分:0)

我会尝试在catalogproduct_id和timestamp上添加索引。看起来它的表扫描给我,没有其他信息。

答案 3 :(得分:0)

您的查询会将表连接到自身以获得最大值。你可以尝试“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;