我有一个PostgreSQL查询,它从一些大约16,000,000条记录的表中选择了多达10,000行。这大约需要3秒。问题是,如果我把完全相同的查询放入一个函数中,它会慢得多,大约需要1分20分钟。
设置概述
(Note实际上,设置稍微复杂一点,但整个问题可以用这种简化的方式来表达。)
我有一个关系型PostgreSQL数据库,其中包含数值网格的单元格和节点。一个网格由许多单元格组成,这些单元格又由一个或多个节点组成,节点基本上是空间中的一个点。因此,有三个表与我的问题相关:GridCells
、GridNodes
和GridCellNodeLinks
。表如下所示:
CREATE TABLE public."GridNodes" (
"id" serial NOT NULL,
PRIMARY KEY ("id")
);
CREATE TABLE public."GridCells" (
"id" serial NOT NULL,
"grid" integer NOT NULL,
PRIMARY KEY ("id")
);
CREATE TABLE public."GridCellNodeLinks" (
"gridCellId" int NOT NULL,
"gridNodeId" int NOT NULL,
FOREIGN KEY ("gridCellId") REFERENCES public."GridCells"("id"),
FOREIGN KEY ("gridNodeId") REFERENCES public."GridNodes"("id")
);
问题:查询链表
我想使用分页来选择属于给定网格的所有节点,每页10,000个节点。为了获得更高的性能,我使用WHERE id >= firstIdOnPage
而不是OFFSET pageNumber * 10000
。
对于网格1(有1,038,240个节点),查询如下所示:
SELECT
N."id"
FROM public."GridCells" G
LEFT JOIN public."GridCellNodeLinks" L
ON G."id" = L."gridCellId"
LEFT JOIN public."GridNodes" N
ON N."id" = L."gridNodeId"
WHERE "grid" = 1
and N."id" >= 1030001
ORDER BY "id" ASC
LIMIT 10000;
如上所述,无论设置如何,这都需要几秒钟才能返回。然而,我将查询放入一个函数中:
(EDIT:实际上,我想将WHERE
和LIMIT
部分中的参数传递给这个函数,而不是硬编码它们。)
CREATE OR REPLACE FUNCTION GetNodes()
RETURNS TABLE(
"id" INT
)
AS
$BODY$
BEGIN
RETURN QUERY
SELECT
N."id"
FROM public."GridCells" G
LEFT JOIN public."GridCellNodeLinks" L
ON G."id" = L."gridCellId"
LEFT JOIN public."GridNodes" N
ON N."id" = L."gridNodeId"
WHERE "grid" = 1
and N."id" >= 1030001
ORDER BY "id" ASC
LIMIT 10000;
END;
$BODY$
LANGUAGE plpgsql
PARALLEL SAFE;
调用该函数SELECT * FROM GetNodes();
需要将近30倍的时间,但只有当它找到的节点数小于LIMIT
时。如果我为页面使用较小的第一个ID,例如N."id" >= 1020001
,或者如果我缩短页面,例如LIMIT 8240
,它仍然在几秒钟内完成。同样,这只发生在函数中,而不是在查询中。
你知道为什么这个函数的行为与“pure”查询有如此大的不同吗?
我所尝试的
- 查询只做索引扫描,所以应该没问题。我试着在函数上做
EXPLAIN ANALYZE
,但正如here所解释的那样,它只给出了一个非决定性的“函数扫描”。使用auto_explain是没有选择的,因为我不是超级用户。 - 有一些方法可以使查询更高效(例如使用子查询),并使其在500毫秒内完成。但这对函数没有影响。所以我想说这超出了这个问题的范围。
- 我添加了
PARALLEL SAFE
,但根本没有效果。 - (编辑)更改为
language sql
也没有效果。
1条答案
按热度按时间e4yzc0pl1#
原来这个问题与*DBeaver*只从查询结果中获取有限数量的行有关(默认为200行)。当查询在函数内时,必须先运行整个函数,然后才能获取200行。否则,DBeaver会秘密地将查询限制为200行,执行计划器可以考虑到这一点,使得查询更快。
该设置位于属性〉编辑器〉数据编辑器下,名为ResultSet获取大小。
将其更改为10000会使我的查询和函数一样慢。因此,函数实际上很好地表示了查询的实际执行情况。我必须回头看看如何提高性能,但这是另一个问题。