在 Postgres 中,如何匹配多个“标签”以获得最佳性能?
Posted
技术标签:
【中文标题】在 Postgres 中,如何匹配多个“标签”以获得最佳性能?【英文标题】:In Postgres, how to match multiple "tags" for best performance? 【发布时间】:2016-12-24 07:11:39 【问题描述】:表格:文章
+--------+------+------------+
| id | title| created |
+--------+------+------------+
| 201 | AAA | 1482561011 |
| 202 | BBB | 1482561099 |
| 203 | CCC | 1482562188 |
+--------+------+------------+
表格:标签
+-----------+------+
| articleid | tagid|
+-----------+------+
| 201 | 11 |
| 201 | 12 |
| 202 | 11 |
| 202 | 13 |
| 202 | 14 |
+-----------+------+
现在如果给定3个标签id,选择每篇文章同时匹配3个标签id的最新10篇文章的最佳索引设计和查询是什么? 我知道有几种方法可以做到这一点,但我关心的是性能,考虑到每个标签中可能有数万篇文章
【问题讨论】:
query to select latest 10 articles
- 请解释你如何定义latest article
?某些表中是否有日期列,但问题中未显示?还是the latest
表示id
列中的最高值?
@krokodilko 我在表格中添加了“已创建”列。是的,最新的是 id 列中的最高值。 id 是“int serial”。
这可能对你来说很有趣:databasesoup.com/2015/01/tag-all-things.html
【参考方案1】:
正如a_horse_with_no_name 提到的,这篇博文有一些非常有趣的性能基准,用于查找匹配多个标签的行:
http://www.databasesoup.com/2015/01/tag-all-things.html
在主表的数组列中存储标签并创建 GIN 索引允许像这样选择行,而无需任何连接:
select id
from articles
where tags @> array[11,13,14]
order by created desc
limit 10;
列和索引可以这样创建:
alter table articles add column tags text[] not null default '';
create index tags_index on articles using gin (tags);
根据博客,使用数组列查找匹配两个标签的行比加入标签表时快 8 到 895 倍。
【讨论】:
自 2015 年以来有变化吗? 最好用最新版本的 PG 进行新测试。有什么引用吗?【参考方案2】:您需要在articles.created
上有一个索引用于排序,并在taggings(articleid, tagid)
上有另一个唯一索引用于查询:
CREATE INDEX ON articles(created);
CREATE UNIQUE INDEX ON taggings(articleid, tagid);
然后只需使用三个taggings
表别名进行选择查询:
SELECT a.* FROM articles a, taggings t1, taggings t2, taggings t3
WHERE a.id=t1.articleid AND a.id=t2.articleid AND a.id=t3.articleid
AND t1.tagid=111 AND t2.tagid=222 AND t3.tagid=333
ORDER BY created DESC LIMIT 10;
【讨论】:
【参考方案3】:select distinct on (a.id) a.*
from articles a
join taggings t on t.articleid = a.id
group by a.id
having array_agg(t.tagid order by t.tagid) = array[11,13,14]
order by a.id, a.created
limit 10;
taggings (articleid, tagid)
上的索引将对此有所帮助。
请注意,上面查找具有恰好这三个标签的文章。如果你想找到那些 至少 这三个标签(可能更多)的标签,你可以更改 having 子句以使用“包含”运算符:
select distinct on (a.id) a.*
from articles a
join taggings t on t.articleid = a.id
where t.tagid in (11,13,14)
group by a.id
having array_agg(t.tagid) @> array[11,13,14]
order by a.id, a.created
limit 10;
在这种情况下,array_agg()
的 order by
是不必要的
【讨论】:
【参考方案4】:就我而言,我必须对每个“标签”应用一个复杂的条件,而@>
没有用。所以我找到了另一种方法:grid sensor array:
ARRAY[
bool_or(true if tag1 is present),
bool_or(true if tag2 is present),
...
] = ARRAY[true, true, ...]
例子:
SELECT a.*
FROM articles a JOIN tags t ON(t.articleid = a.id)
GROUP BY a.id
HAVING
ARRAY[
bool_or(t.tagid == 11),
bool_or(t.tagid == 13),
bool_or(t.tagid == 14)
] == ARRAY[true, true, true]
它的性能很差,但对于多对多关系具有很大的灵活性。
【讨论】:
以上是关于在 Postgres 中,如何匹配多个“标签”以获得最佳性能?的主要内容,如果未能解决你的问题,请参考以下文章