带有多个 JOIN 的非常慢的 PSQL 查询

Posted

技术标签:

【中文标题】带有多个 JOIN 的非常慢的 PSQL 查询【英文标题】:Very slow PSQL query with several JOINs 【发布时间】:2022-01-22 07:05:36 【问题描述】:

我在 PostgreSQL 中遇到了超慢查询的问题。

DB ER图部分重点关注这个问题

culture 表有 6 条记录,microclimate_value 表大约有 190k 条记录,location 表有 3 条记录,表 crop_yield 大约有 40k 条记录。

查询

SELECT max(cy.value) AS yield, EXTRACT(YEAR FROM cy.date) AS year
FROM microclimate_value AS mv
JOIN culture AS c ON mv.id_culture = c.id
JOIN location AS l ON mv.id_location = l.id
JOIN crop_yield AS cy ON l.id = cy.id_location
WHERE c.id = :cultureId AND l.id = :locationId
GROUP BY year
ORDER BY year

对于给定的 :cultureId (文化表中的主键)和 :locationId (位置表中的主键),此查询应产生每年(crop_yield 表)的最大值。它看起来像这样(来自crop_yield 表的yield == value 列):

[
    
        "year": 2014,
        "yield": 0.0
    ,
    
        "year": 2015,
        "yield": 1972.6590590838807
    ,
    
        "year": 2016,
        "yield": 3254.6370785040726
    ,
    
        "year": 2017,
        "yield": 2335.5804000689095
    ,
    
        "year": 2018,
        "yield": 3345.2244602819046
    ,
    
        "year": 2019,
        "yield": 3004.7096788680583
    ,
    
        "year": 2020,
        "yield": 2920.8721807693764
    ,
    
        "year": 2021,
        "yield": 0.0
    
]

增强尝试

最初,此查询大约需要 10 分钟,因此优化或查询本身存在一些大问题。我做的第一件事是在 microclimate_valuecrop_yield 表中索引外键,这会带来更好的性能,但查询仍然需要 2-3 分钟才能执行。

有人对如何改进有任何提示吗?考虑到我仍在学习 SQL,我愿意接受任何提示,包括根据需要更改整个架构。

提前致谢!

编辑

    添加 EXPLAIN PSQL

    在添加索引后添加第二个 EXPLAIN ANALYZE PSQL:

【问题讨论】:

您可以发布您的查询的EXPLAIN 的输出吗?这可能会揭示一些可以通过索引改进的缓慢操作。 @SebDieBln 使用 EXPLAIN 的结果编辑了帖子。如果您想从此命令中获得更具体的内容,请告诉我。 是否为year 帮助创建索引? CREATE INDEX ON "crop_yield" ((EXTRACT(YEAR FROM "date"))) 使用 EXPLAIN(ANALYZE, VERBOSE, BUFFERS),向您显示查询计划和您遇到问题的地方。只是一个查询计划没有任何意义,它可能是有史以来最好的计划。 @FrankHeikens 不确定我是否做得对,但计划如下:explain.dalibo.com/plan/WUd。 【参考方案1】:

在单个索引中进行一些列组合。我将从这个开始,在搜索数据后摆脱所有过滤:

CREATE INDEX idx_crop_yield_id_location_year_value ON crop_yield(id_location, (EXTRACT ( YEAR FROM DATE )), value);

CREATE INDEX idx_microclimate_value_id_location_id_culture ON microclimate_value(id_location, id_culture);

也许列中的不同顺序效果更好,这是您必须找出的。

我也会将未使用的表“文化”排除在外:

SELECT MAX( cy.VALUE ) AS yield,
    EXTRACT ( YEAR FROM cy.DATE ) AS YEAR 
FROM
    microclimate_value AS mv
        JOIN LOCATION AS l ON mv.id_location = l.ID 
        JOIN crop_yield AS cy ON l.ID = cy.id_location 
WHERE
    mv.id_culture = : cultureId 
AND l.ID = : locationId 
GROUP BY    YEAR 
ORDER BY    YEAR;

在查询或索引每次更改后,再次运行 EXPLAIN(ANALYZE, VERBOSE, BUFFERS)。

【讨论】:

1.这导致查询时间大约为 1 分 10 秒,仍然存在问题。 2. 更改列顺序时是否需要删除之前创建的索引? 是的,您可以删除一些索引。但是你必须一遍又一遍地使用 EXPLAIN(ANALYZE, VERBOSE, BUFFERS)。如果没有这些信息,没有人可以帮助您,我们无权访问您的数据库【参考方案2】:

根据您的explain analyzelocation=2 and id_culture=1 有 10,970 行 microclimate_valuelocation=2crop_yield 中也有 12,316 行。

由于这两个表的连接没有其他条件,数据库必须在内存中创建一个 10,970*12,316=135,106,520 行的表,然后将其结果分组。这可能需要一些时间……

我认为您在查询中缺少某些条件。您确定 microclimate_value.date 和crop_yield.date 不应该有相同的日期吗?因为,恕我直言,没有它,查询没有多大意义。


如果与这些日期没有关联,那么 microclimate_value 中可能有用的唯一信息是那里是否存在匹配的 location_id=? and culture_id=?

select
  max(value) as max_value,
  extract(year from date) as year,
from crop_yield
where location_id=?
  and exists(
    select 1
    from microclimate_value
    where location_id=? and culture_id=?
  )
group by year

如果它们在某个地方匹配,您要么会得到结果,要么不会得到任何结果。这个架构的设计似乎有问题。

【讨论】:

不,这两个表上没有日期相等的条件。我知道为什么它没有意义,但是您将如何使用给定的参数(cultureId 和 locationId)编写对 DB 的当前状态的查询,以便从按年分组的crop_yield 中提取最大值? 我试图澄清一下。

以上是关于带有多个 JOIN 的非常慢的 PSQL 查询的主要内容,如果未能解决你的问题,请参考以下文章

执行多个查询时,“psql -c”和“psql -f”有啥区别?

带有 INNER JOIN 和 WHERE 的 SQL 查询

带有“not in”的 SQL 查询非常慢

关于Mysql使用left join写查询语句执行很慢的问题解决

优化查询数据慢的方式

如何使用多个 INNER JOIN 加快查询速度