我试图确定是否有低成本"以下查询的优化。我们已经实施了一个系统,其中包括“门票”和“门票”。赚取积分'因此可以排名。为了支持分析类型的查询,我们将每个票证的等级(票证可以绑定)与票证一起存储。
我发现,在规模上,更新此排名非常缓慢。我试图在一组"门票"上运行下面的场景。这大约是20k门票。
我希望有人可以帮助确定原因并提供一些帮助。
我们在postgres 9.3.6
这是一个简化的票证表架构:
ogs_1=> \d api_ticket
Table "public.api_ticket"
Column | Type | Modifiers
------------------------------+--------------------------+---------------------------------------------------------
id | integer | not null default nextval('api_ticket_id_seq'::regclass)
status | character varying(3) | not null
points_earned | integer | not null
rank | integer | not null
event_id | integer | not null
user_id | integer | not null
Indexes:
"api_ticket_pkey" PRIMARY KEY, btree (id)
"api_ticket_4437cfac" btree (event_id)
"api_ticket_e8701ad4" btree (user_id)
"api_ticket_points_earned_idx" btree (points_earned)
"api_ticket_rank_idx" btree ("rank")
Foreign-key constraints:
"api_ticket_event_id_598c97289edc0e3e_fk_api_event_id" FOREIGN KEY (event_id) REFERENCES api_event(id) DEFERRABLE INITIALLY DEFERRED
(user_id) REFERENCES auth_user(id) DEFERRABLE INITIALLY DEFERRED
这是我正在执行的查询:
UPDATE api_ticket t SET rank = (
SELECT rank
FROM (SELECT Rank() over (
Partition BY event_id ORDER BY points_earned DESC
) as rank, id
FROM api_ticket tt
WHERE event_id = t.event_id
AND tt.status != 'x'
) as r
WHERE r.id = t.id
)
WHERE event_id = <EVENT_ID> AND t.status != 'x';
这是关于一组大约10k行的解释:
Update on api_ticket t (cost=0.00..1852176.70 rows=9646 width=88) (actual time=1254035.623..1254035.623 rows=0 loops=1)
-> Seq Scan on api_ticket t (cost=0.00..1852176.70 rows=9646 width=88) (actual time=121.611..1253148.416 rows=9748 loops=1)
Filter: (((status)::text <> 'x'::text) AND (event_id = 207))
Rows Removed by Filter: 10
SubPlan 1
-> Subquery Scan on r (cost=159.78..191.97 rows=1 width=8) (actual time=87.466..128.537 rows=1 loops=9748)
Filter: (r.id = t.id)
Rows Removed by Filter: 9747
-> WindowAgg (cost=159.78..178.55 rows=1073 width=12) (actual time=46.389..108.954 rows=9748 loops=9748)
-> Sort (cost=159.78..162.46 rows=1073 width=12) (actual time=46.370..66.163 rows=9748 loops=9748)
Sort Key: tt.points_earned
Sort Method: quicksort Memory: 799kB
-> Index Scan using api_ticket_4437cfac on api_ticket tt (cost=0.29..105.77 rows=1073 width=12) (actual time=2.698..26.448 rows=9748 loops=9748)
Index Cond: (event_id = t.event_id)
Filter: ((status)::text <> 'x'::text)
Total runtime: 1254036.583 ms
答案 0 :(得分:2)
相关子查询必须为每行 执行 (在您的示例中为20k次)。这仅适用于小行数或计算需要的行。
在我们加入之前,此派生表计算 一次 :
UPDATE api_ticket t
SET rank = tt.rnk
FROM (
SELECT tt.id
, rank() OVER (PARTITION BY tt.event_id
ORDER BY tt.points_earned DESC) AS rnk
FROM api_ticket tt
WHERE tt.status <> 'x'
AND tt.event_id = <EVENT_ID>
) tt
WHERE t.id = tt.id
AND t.rank <> tt.rnk; -- avoid empty updates
应该快一点。 :)
我添加的最后一个条件排除了空更新:
只有偶尔才有新排名可以成为旧排名才有意义。否则将其删除。
我们无需在外部查询中重复AND t.status != 'x'
,因为我们加入了PK列id
,它在两侧都是相同的值。
标准的SQL不等式运算符是<>
,即使Postgres也支持!=
,而是使用标准运算符。
同样将谓词event_id = <EVENT_ID>
下推到子查询中。无需为任何其他event_id
计算数字。这是从原始的外部查询传下来的。在重写的查询中,我们最好将谓词完全拉入子查询中。由于我们使用PARTITION BY tt.event_id
,因此不会混淆排名。