Jef*_*era 6 postgresql performance join paging postgresql-performance
简介: 我有一个简单的数据库模式,但即使只有几十条记录,基本查询的性能也已经成为一个问题。
数据库:PostgreSQL 9.6
简化架构:
CREATE TABLE article (
id bigint PRIMARY KEY,
title text NOT NULL,
score int NOT NULL
);
CREATE TABLE tag (
id bigint PRIMARY KEY,
name text NOT NULL
);
CREATE TABLE article_tag (
article_id bigint NOT NULL REFERENCES article (id),
tag_id bigint NOT NULL REFERENCES tag (id),
PRIMARY KEY (article_id, tag_id)
);
CREATE INDEX ON article (score);
Run Code Online (Sandbox Code Playgroud)
生产数据信息:
所有表都是读/写的。写入量低,每几分钟左右只有一个新记录。
大概记录数:
每篇文章平均 5 个标签。
问题:我想创建一个视图article_tags
,其中包含每个文章记录的标签数组,可以按顺序排序article.score
和分页,也可以不加过滤。
在我的第一次尝试中,我惊讶地发现查询花费了大约 350 毫秒来执行并且没有使用索引。在随后的尝试中,我能够将其降低到约 5 毫秒,但我不明白发生了什么。我希望所有这些查询花费相同的时间。我在这里缺少什么关键概念?
尝试(SQL 小提琴):
对于分页,LIMIT
(和OFFSET
) 是简单的,但对于更大的表格来说通常效率低下的工具。您的测试LIMIT 10
只显示了冰山一角。无论您选择哪个查询,性能都会随着 的增长OFFSET
而降低。
如果您没有或只有很少的并发写入访问权限,则更好的解决方案是MATERIALIZED VIEW
添加行号,并在其上加上索引。您的所有查询都按行号选择行。
在并发写入负载下,这样的 MV 很快就会过时(但CONCURRENTLY
每 N 分钟刷新一次MV 之类的妥协可能是可以接受的)。
LIMIT
/OFFSET
根本无法正常工作,因为“下一页”是那里的移动目标,并且LIMIT
/OFFSET
无法应对。最好的技术取决于未公开的信息。
有关的:
您的索引通常看起来不错。但是您的评论表明该表tag
有很多行。通常,像 那样的表上的写负载很少tag
,这非常适合仅索引支持。所以添加一个多列(“覆盖”)索引:
CREATE INDEX ON tag(id, name);
Run Code Online (Sandbox Code Playgroud)
有关的:
如果您实际上不需要更多页面(严格来说这不是“分页”),那么任何查询样式都可以减少从相关表中检索详细信息article
之前的合格行(昂贵)。您的“有限子查询”(3.)和“横向连接”(4.)解决方案很好。但你可以做得更好:
为变体使用ARRAY
构造函数LATERAL
:
SELECT a.id, a.title, a.score, tags.names
FROM article a
LEFT JOIN LATERAL (
SELECT ARRAY (
SELECT t.name
FROM article_tag a_t
JOIN tag t ON t.id = a_t.tag_id
WHERE a_t.article_id = a.id
-- ORDER BY t.id -- optionally sort array elements
)
) AS tags(names) ON true
ORDER BY a.score DESC
LIMIT 10;
Run Code Online (Sandbox Code Playgroud)
在LATERAL
一个子查询组装标签单 article_id
的时间,所以GROUP BY article_id
是多余的,以及连接条件ON tags.article_id = article.id
和基本ARRAY
构造是比便宜的array_agg(tag.name)
为剩余的简单情况。
或者使用低相关的子查询,通常更快,但:
SELECT a.id, a.title, a.score
, ARRAY (
SELECT t.name
FROM article_tag a_t
JOIN tag t ON t.id = a_t.tag_id
WHERE a_t.article_id = a.id
-- ORDER BY t.id -- optionally sort array elements
) AS names
FROM article a
ORDER BY a.score DESC
LIMIT 10;
Run Code Online (Sandbox Code Playgroud)
db<>fiddle here
SQL Fiddle