存储'排名' Postgres比赛

时间:2015-07-20 18:38:20

标签: sql postgresql sql-update correlated-subquery postgresql-performance

我试图确定是否有低成本"以下查询的优化。我们已经实施了一个系统,其中包括“门票”和“门票”。赚取积分'因此可以排名。为了支持分析类型的查询,我们将每个票证的等级(票证可以绑定)与票证一起存储。

我发现,在规模上,更新此排名非常缓慢。我试图在一组"门票"上运行下面的场景。这大约是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

1 个答案:

答案 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,因此不会混淆排名。