簡體   English   中英

在Postgres中使用計數不同的慢查詢

[英]Slow query in postgres using count distinct

我的目標是創建一個查詢,該查詢將返回在365天的窗口中購買的唯一客戶的數量。 我在postgres中創建了以下查詢,結果查詢非常慢。 我的表是812,024行,僅包含訂單日期和客戶ID。 當我刪除非重復語句時,我可以得到查詢以在大約60秒鍾內返回結果,但尚未完成。 我在(order_date,id)上創建了一個索引。 我是SQL的新手,這確實是我第一次做任何事情,並且整日試圖找到解決此問題的方法之后,即使我找不到任何可以工作的東西,已經看到了很多與眾不同的緩慢表現。

SELECT
    (d1.Ordered) AS Ordered,
    COUNT(distinct d2.ID) Users
FROM
(
    SELECT order_date AS Ordered
    FROM orders
    GROUP BY order_date
) d1 
INNER JOIN
(
    SELECT order_date AS Ordered, id
    FROM orders
) d2
ON d2.Ordered BETWEEN d1.Ordered - 364 AND d1.Ordered
GROUP BY d1.Ordered
ORDER BY d1.Ordered

"Sort  (cost=3541596.30..3541596.80 rows=200 width=29)"
"  Sort Key: orders_1.order_date"
"  ->  HashAggregate  (cost=3541586.66..3541588.66 rows=200 width=29)"
"        ->  Nested Loop  (cost=16121.73..3040838.52 rows=100149627 width=29)"
"              ->  HashAggregate  (cost=16121.30..16132.40 rows=1110 width=4)"
"                    ->  Seq Scan on orders orders_1  (cost=0.00..14091.24 rows=812024 width=4)"
"              ->  Index Only Scan using x on orders  (cost=0.43..1822.70 rows=90225 width=29)"
"                    Index Cond: ((order_date >= (orders_1.order_date - 364)) AND (order_date <= orders_1.order_date))"

不需要自我連接,使用generate_series

select
    g.order_date as "Ordered",
    count(distinct o.id) as "Users"
from
    generate_series(
        (select min(order_date) from orders),
        (select max(order_date) from orders),
        '1 day'
    ) g (order_date)
    left join
    orders o on o.order_date between g.order_date - 364 and g.order_date
group by 1
order by 1

您尚未顯示架構,因此請在此處進行一些猜測。 適當更改列名稱等。

SELECT 
  count(DISTINCT users.user_id)
FROM users
INNER JOIN order_date ON (users.user_id = orders.user_id)
WHERE orders.order_date > current_date - INTERVAL '1' YEAR;

要么

SELECT 
  count(users.user_id)
FROM users
INNER JOIN order_date ON (users.user_id = orders.user_id)
WHERE orders.order_date > current_date - INTERVAL '1' YEAR
GROUP BY users.user_id;

假設實際date類型。

SELECT d.day, count(distinct o.id) AS users_past_year
FROM  (
   SELECT generate_series(min(order_date), max(order_date), '1 day')::date AS day
   FROM   orders         -- single query
   ) d
LEFT JOIN (              -- fold duplicates on same day right away
   SELECT id, order_date
   FROM   orders
   GROUP  BY 1,2
   ) o ON o.order_date >  d.day - interval '1 year' -- exclude
      AND o.order_date <= d.day                     -- include
GROUP  BY 1
ORDER  BY 1;

只有在同一天,首先折疊同一位用戶的多次購買才有意義。 否則,省略該步驟並直接左聯接到表orders會更快。

orders.id是用戶的ID很奇怪。 應命名為user_id

如果您對SELECT列表中的generate_series()感到不滿意(效果很好),則可以在Postgres 9.3+中將其替換為LATERAL JOIN

FROM  (SELECT min(order_date) AS a
            , max(order_date) AS z FROM orders) x
    , generate_series(x.a, x.z, '1 day') AS d(day)
LEFT JOIN ...

請注意,在這種情況下, day是類型的timestamp 工作原理相同。 您可能想要投射。

一般性能提示

我了解這是單個用戶的只讀表。 這簡化了事情。
您似乎已經有了一個索引:

CREATE INDEX orders_mult_idx ON orders (order_date, id);

那很好。

可以嘗試的一些事情:

基本

當然,通常的性能建議適用於:
https://wiki.postgresql.org/wiki/Slow_Query_Questions
https://wiki.postgresql.org/wiki/Performance_Optimization

精簡表

使用此索引對表進行集群一次:

CLUSTER orders USING orders_mult_idx;

這應該有所幫助。 它還可以在表上有效地運行VACUUM FULL ,從而刪除所有無效行並壓縮表(如果適用)。

更好的統計

ALTER TABLE orders ALTER COLUMN number SET STATISTICS 1000;
ANALYZE orders;

此處說明:

分配更多的RAM

確保分配了足夠的資源。 特別是對於shared_bufferswork_mem 您可以在會話中臨時執行此操作。

試驗計划者方法

嘗試禁用嵌套循環( enable_nestloop )(僅在您的會話中)。 也許哈希聯接更快。 (不過,我會感到驚訝。)

SET enable_nestedloop = off;
-- test ...

RESET enable_nestedloop;

臨時表

由於這本質上似乎是“臨時表”,因此您可以嘗試使其成為僅保存在RAM中的實際臨時表。 您需要足夠的RAM來分配足夠的temp_buffers 詳細說明:

確保手動運行ANALYZE 臨時表不包含在自動真空中。

暫無
暫無

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

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