簡體   English   中英

慢的SQL事務從Postgres中的表中獲取最新的時間戳行

[英]Slow SQL transaction getting latest timestamp rows from table in Postgres

我在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和timestamp)。 我有一個主鍵,它是一個自動遞增的索引,我猜它與根本沒有索引一樣好。

編輯2:添加@Erwin建議的部分索引后, CREATE INDEX my_partial_idx ON ta_price (catalogproduct_id, timestamp) WHERE buy > sell; ,我使用來自@wildplasser的查詢大約10-12秒的查詢時間。 為了進一步說明,我的表格是一段時間內產品價格(購買和出售)的快照。 在任何給定時間,我想知道當前(截至其最新快照時間)哪些產品具有“ buy > sell

經過考慮后修改答案

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- 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這樣的簡單索引都將有助於:
在ta_price上創建索引my_idx(catalogproduct_id,時間戳記);

不過,有一種更好的方法。
子選擇中的無條件max()將導致順序表掃描,而不管索引如何。 對於2.2m行,這樣的操作將永遠不會很快。
JOIN條件與外部SELECTWHERE子句結合使用,將從上述索引中受益。 取決於buy > sell的選擇性, 部分索引會稍快或明顯快一些,相應地,在磁盤和RAM中會更小:

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

在這種情況下,索引中列的順序無關緊要。 它還可以加快查詢的第二種速度。

您提到表格是出於“歷史性”目的嗎? 如果這意味着沒有新數據,則可以通過實例化視圖大大加快處理速度。


附帶說明:我不會將timestamp用作列名。 在PostgreSQL中允許使用,但是在所有SQL標准中都是保留字


OK, 最后第一件事 :為2.2米行的表,你需要的方式更多的資源Postgres的具有開箱即用。

  • 查看您的postgresql.conf文件,並開始檢查shared_bufferswork_mem的設置。
  • 請查閱postgres Wiki進行性能調整
  • 查閱有關資源消耗的精美手冊
  • 查閱有關計划者費用的精美手冊
  • 增加這些統計信息設置:
    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;

  • 確保自動真空正在運行。 如有疑問,請運行VACUUM ANALYZE ta_price ,查看其是否有效。


我在資源有限的pg 8.4安裝上測試了wildplasser的測試設置( 這非常有幫助! )。 這是EXPLAIN ANYLYZE總運行時間

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

帶有附加(購買>出售)子句的變體2:

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

恢復

AH的版本需要更長的時間(與您報告的結果相同)。 窗口功能往往很慢,尤其是在較舊版本的postgres上。 我的替代查詢的速度是預期的兩倍。 問題是,是否需要不同的結果-也許不是。

無論如何,那是30萬行。 在使用5年的服務器上,資源有限(但大多數情況下都是適當的設置)的版本8.4的查詢時間為0.5-1s。 擁有不錯的機器和不錯的設置(足夠的RAM!),您至少應將其降至10秒以下。

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中的文本不清楚實際需要什么)。

更新:“時間戳”是保留字。 我改變了一下。 還添加了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中的30萬條記錄)

  ------------------------------------------------------------------------------
 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)

我會嘗試在catalogproduct_id和timestamp上添加索引。 看起來是沒有其他信息的表格掃描給我。

您的查詢將表與其自身聯系起來以獲得最大值。 您可以嘗試“ 窗口函數 ”來防止這種情況-也許它們在您的情況下效果更好:

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;

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM