带有多个 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_value 和 crop_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 analyze
,location=2 and id_culture=1
有 10,970 行 microclimate_value
。 location=2
在crop_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 查询