我在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
。
答案 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;
buy
和sell
在您的问题中不合格。根据{{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开箱即用。
timestamp
和shared_buffers
的设置是否有效。增加这些统计设置:
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;