【问题标题】:How does PostgreSQL approach a 1 + n query?PostgreSQL 如何处理 1 + n 查询?
【发布时间】:2018-05-21 03:10:36
【问题描述】:

我正在针对 Sakila 数据库进行测试,请参阅 http://www.postgresqltutorial.com/postgresql-sample-database/。该数据库包含三个关系:

  • 电影:film_id、标题
  • 演员:actor_id、名字
  • film_actor:film_id、actor_id

我想列出所有电影,对于每部电影,我想列出在该特定电影中扮演的所有演员。我以以下查询结束:

select   film_id, title, array
         (
           select     first_name
           from       actor
           inner join film_actor
           on         actor.actor_id = film_actor.actor_id
           where      film_actor.film_id = film.film_id
         ) as actors
from     film
order by title;

从概念上讲,这是一个1 + n 查询

one query: get films
n queries: for each film f
             f.actors = array(get actors playing in f)

我一直认为应该不惜一切代价避免 1 + n 个查询,因为这不能很好地扩展。

所以这让我想知道:PostgreSQL 是如何在内部实现这一点的?假设我们有 1000 部电影,它是否在内部执行 1000 个select actor.first_name from actor inner join ... 查询?或者 PostgreSQL 在这方面更聪明,它会像下面这样吗?

1. one query:  get films
2. one query:  get actors related to these films while keeping reference to film_id
3. internally: for each film f
                 f.actors = array(subset of (2) according to film_id)

这会执行 1 + 1 个查询。

【问题讨论】:

标签: sql postgresql


【解决方案1】:

您正在考虑嵌套循环。这是您在使用关系数据库时应该克服的问题(除非您使用的是 MySQL)。

您所说的“1 + n”是一个嵌套循环:您扫描一个表,对于找到的每一行,您扫描另一个表。

按照你的 SQL 查询的编写方式,PostgreSQL 只能执行嵌套循环。

只要外部表(在您的示例中为film)的行数很少,这很好。一旦外表变大,性能会迅速下降。

除了嵌套循环,PostgreSQL 还有另外两种连接策略:

  • 散列连接: 扫描内部关系并创建散列结构,其中散列键是连接键。然后扫描外部关系,并为找到的每一行探测哈希。

    将其视为一种哈希联接,但在内部,您拥有高效的内存数据结构。

  • 合并联接:两个表都按联接键排序,并通过同时扫描结果进行合并。

建议您编写查询时不要使用“相关子查询”,以便 PostgreSQL 可以选择最佳连接策略:

SELECT film_id, f.title, array_agg(a.first_name)
FROM film f
   LEFT JOIN film_actor fa USING (film_id)
   LEFT JOIN actor a USING (actor_id)
GROUP BY f.title
ORDER BY f.title;

使用左外连接以便即使电影没有演员也能得到结果。

【讨论】:

  • 这很有趣,谢谢!我必须在您的查询中添加GROUP BY f.film_id 是否正确?分组不会引入额外的性能损失,而相关子查询没有吗?
  • 那么一般来说,关联子查询最好用左连接代替?
  • 我在问自己为什么查询规划器无法将相关子查询自己转换为左连接:stackoverflow.com/questions/50434001/…
  • 我忘记了GROUB BY - 已修复。当然,这会产生一些影响,但嵌套的垂耳兔也是如此。将它们与EXPLAIN (ANALYZE) 进行比较,您会发现哪个更快。是的,编写左连接比编写相关子查询更好。也许优化器足够聪明,可以展平子查询,但没有必要让 PostgreSQL 变得比必要的更难。
  • 我同意这一点,阅读确实更好,我们不应该让它比需要的更难。
【解决方案2】:

这可能更适合评论,但它太长了。

虽然我遵循您查询的逻辑,但我更喜欢将其表达为:

select f.film_id, f.title,
       (select array_agg(a.first_name)
        from actor a inner join
             film_actor fa
             on a.actor_id = fa.actor_id
        where fa.film_id = f.film_id
       ) as actors
from film f
order by f.title;

明确的array_agg() 阐明了逻辑。您正在聚合子查询,将结果作为一个数组组合在一起,然后将其作为一列包含在外部查询中。

【讨论】:

  • 你说得对,使用array_agg 可以更清楚地说明我们在做什么。谢谢你给我建议。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-06-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-10-14
相关资源
最近更新 更多