【问题标题】:PostgreSQL aggregate before join vs after join performance difference加入前与加入后的 PostgreSQL 聚合性能差异
【发布时间】:2018-08-27 15:42:50
【问题描述】:

我有 3 张桌子:

create table cart (
  id       bigserial primary key,
  buyer_id bigint unique not null
);


create table contact_person (
  id           bigserial primary key,
  cart_id      bigint references cart (id) not null unique,
  phone_number jsonb,
  first_name   VARCHAR,
  middle_name  VARCHAR,
  last_name    VARCHAR
);

create table cart_items (
  id      bigserial primary key,
  item_id bigint                      not null,
  cart_id bigint references cart (id) not null,
  count   int                         not null,
  unique (item_id, cart_id)
);

cart:contact_person 相关为 1:1
购物车:cart_items 1:N

我想按购物车 ID 聚合所有购物车项目字段。 有两种选择:

1) 加入前聚合:

select c.id       as id,
               c.buyer_id as buyer_id,
               cp.id      as contact_id,
               cp.phone_number,
               cp.first_name,
               cp.middle_name,
               cp.last_name,
               ci.ids, ci.item_ids, ci.counts
        from cart c
               inner join contact_person cp on c.id = cp.cart_id
               left join (select cart_id, array_agg(id) as ids, array_agg(item_id) as item_ids, array_agg(count) as counts
                          from cart_items ci
                          group by cart_id) ci on ci.cart_id = c.id
        where c.buyer_id = :buyerId;

2) 加入后聚合:

select c.id       as id,
               c.buyer_id as buyer_id,
               cp.id      as contact_id,
               cp.phone_number,
               cp.first_name,
               cp.middle_name,
               cp.last_name,
               array_agg(ci.id) as ids,
               array_agg(ci.item_id) as item_ids,
               array_agg(ci.count) as counts
        from cart c
               inner join contact_person cp on c.id = cp.cart_id
               left join cart_items ci on ci.cart_id = c.id
        where c.buyer_id = :buyerId
group by c.id, cp.id;

正如 Explain 所示,join 后的聚合查询要快得多。 查询计划确实不同,但我无法解释为什么在聚合之前它们的成本如此之高。

1) 聚合之前:

Nested Loop  (cost=108.97..141.16 rows=1 width=248)
  ->  Merge Left Join  (cost=108.82..132.96 rows=1 width=112)
        Merge Cond: (c.id = ci.cart_id)
        ->  Sort  (cost=8.18..8.19 rows=1 width=16)
              Sort Key: c.id
              ->  Index Scan using cart_buyer_id_key on cart c  (cost=0.15..8.17 rows=1 width=16)
                    Index Cond: (buyer_id = 1)
        ->  GroupAggregate  (cost=100.64..122.26 rows=200 width=104)
              Group Key: ci.cart_id
              ->  Sort  (cost=100.64..104.26 rows=1450 width=28)
                    Sort Key: ci.cart_id
                    ->  Seq Scan on cart_items ci  (cost=0.00..24.50 rows=1450 width=28)
  ->  Index Scan using contact_person_cart_id_key on contact_person cp  (cost=0.15..8.17 rows=1 width=144)
        Index Cond: (cart_id = c.id)

2) 聚合后:

GroupAggregate  (cost=41.62..41.66 rows=1 width=248)
  Group Key: c.id, cp.id
  ->  Sort  (cost=41.62..41.63 rows=1 width=172)
        Sort Key: c.id, cp.id
        ->  Nested Loop Left Join  (cost=15.33..41.61 rows=1 width=172)
              ->  Nested Loop  (cost=0.30..16.37 rows=1 width=152)
                    ->  Index Scan using cart_buyer_id_key on cart c  (cost=0.15..8.17 rows=1 width=16)
                          Index Cond: (buyer_id = 1)
                    ->  Index Scan using contact_person_cart_id_key on contact_person cp  (cost=0.15..8.17 rows=1 width=144)
                          Index Cond: (cart_id = c.id)
              ->  Bitmap Heap Scan on cart_items ci  (cost=15.03..25.17 rows=7 width=28)
                    Recheck Cond: (cart_id = c.id)
                    ->  Bitmap Index Scan on cart_items_item_id_cart_id_key  (cost=0.00..15.03 rows=7 width=0)
                          Index Cond: (cart_id = c.id)

我想在 cart_id 字段上添加一个索引到 cart_items,这有效地加速了查询,但是在第一种情况下,在第二种情况下。 您如何解释这种差异?

【问题讨论】:

  • [正如你自己发现的那样] FK 没有支持索引 cart_items.cart_id --> carts.id (这可能导致需要排序步骤)注意:查询都是相对的小型的、基于成本的计划不适用于少数人。

标签: sql postgresql sql-execution-plan


【解决方案1】:

这样想:在您之前的示例中,您要连接一个表和一个“动态”视图,必须在连接之前生成它。

在您的“之后”示例中,您将加入 2 个表,然后进行聚合。连接本身更快,不需要创建、排序等。在你收集完所有数据后聚合数据应该更快,当你不消除任何行时......无论如何,连接要简单得多。

【讨论】:

  • 听起来合乎逻辑。但是如果连接的数量在增长,那么出于某种原因,在连接之前使用聚合的选项会快得多。例如,当我在之后使用聚合时遇到一个问题,并且在答案中给出了一个在加入之前具有聚合的变体,他出来得更快。当连接数量增加时,这种速度差距会增加。 [stackoverflow.com/questions/51825480/…
  • 我看看能不能复制。计划者可以做一些奇怪的魔法,这就是为什么我一直是 Oracle 的“提示”系统的粉丝,它可以告诉优化器在某些情况下你想要做什么。 Postgres 一直在努力奋斗,通常您只需调整语法,直到优化器完成您想要的操作。但同样,涉及的一些事情变得非常复杂,因为当您涉及多个表时,需要考虑很多统计信息。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-01-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多