用于 JOIN 查询的 Postgresql 分页性能
Posted
技术标签:
【中文标题】用于 JOIN 查询的 Postgresql 分页性能【英文标题】:Postgresql paging perfomance for JOIN query 【发布时间】:2020-08-16 09:39:29 【问题描述】:我有两张桌子:
products
~ 2000 万条记录
tags
~3500 万条记录
并且对此类查询有性能问题:
SELECT products.name
FROM products
INNER JOIN tags ON tags.product_id=products.id
WHERE
products.categoryid IN (1,2,3..) AND
tags.id = 3
ORDER BY products.position
LIMIT 64 OFFSET 0;
当 OFFSET 很小时,一切都很好。当 OFFSET 大约 >1000 时,查询执行 10-80 秒。
已经有ORDER BY position
(产品表)和product_id, id
(标签表)的索引。
(OFFSET=100)
Limit (cost=85705.10..147411.97 rows=72 width=667) (actual time=1623.013..2334.542 rows=72 loops=1)
-> Nested Loop (cost=1.13..67736996.34 rows=79036 width=667) (actual time=4.898..2334.493 rows=172 loops=1)
-> Index Scan using items_sorting_index on products (cost=0.56..61905349.96 rows=1513209 width=667) (actual time=0.083..2188.800 rows=17547 loops=1)
Filter: (categoryid = ANY ('1,2,3,4,5,6,7,8,9,11,12,13,14,15,16,17,18,19,20'::bigint[]))
Rows Removed by Filter: 638658
-> Index Only Scan using index_tags_on_product_id_and_tag_id on tags (cost=0.56..3.84 rows=1 width=4) (actual time=0.008..0.008 rows=0 loops=17547)
Index Cond: ((product_id = products.id) AND (id = 3))
Heap Fetches: 28
Planning time: 1.345 ms
Execution time: 2334.608 ms
tags.id
在查询中可以不同,不同标签的结果元素也不同,所以我觉得不能用cursor-field这样的方法。
如何优化这个查询?
【问题讨论】:
您的统计数据看起来有点偏离(产品表中的预期rows=1513209
与实际rows=17547
)。您可能想先在两个表上运行ANALYZE
。您可以尝试的另一件事是让items_sorting_index
索引覆盖name
列(请参阅the docs)。
@Marth 统计信息已关闭,因为“预期”行没有考虑 LIMIT,而实际行则考虑了。
我不明白你对光标的反对意见。还是“光标字段”与光标不同?
@jjanes 将字段添加到表中,这将有助于计算页面位置(如allyouneedisbackend.com/blog/2017/09/24/…)。这不是我的问题
键集分页应该可以正常工作,只要人们实际上是在逐步浏览数据,而不是想在没有先看到 1-49 的情况下跳到第 50 页。如果 position 不是唯一的,则需要在 ORDER BY 中添加一个 tie breaker。
【参考方案1】:
您可以在加入前通过过滤器标签表减少数据集,例如:
SELECT products.name
FROM products
INNER JOIN (
SELECT id FROM tags WHERE tags.id = 3
) tags ON tags.product_id=products.id
WHERE
products.categoryid IN (1,2,3..)
ORDER BY products.position
LIMIT 64 OFFSET 0;
或者分别过滤两个表并连接结果:
SELECT products.name
FROM (
SELECT name, position FROM products WHERE products.categoryid IN (1,2,3..)
) products
INNER JOIN (
SELECT id FROM tags WHERE tags.id = 3
) tags ON tags.product_id=products.id
ORDER BY products.position
LIMIT 64 OFFSET 0;
【讨论】:
ЕслимыделаемJOINдвухтаблиц,записисравниваютсяпоследовательноизподходящихпокритериямилисоздаютсядвабольшихнабора,которыеобъединяютсяпоусловию? ВовторомвашемпримерееслиуменяБудетмиллионызаписейсtags.id= 3,ТонебудетЛиэтоТяжелодлябдсоздатьтакойdatasetдляпоследушего加入? span> Уважаемый @НиколайАгеев,请用英文写下您的评论,以便所有 SO 用户都能阅读和协作。关于我的建议,测试它的最佳方法是测试您的数据集。只需在此处运行查询和评论是否有帮助【参考方案2】:您可以很好地使用键集分页。初始查询是:
SELECT products.name, products.position, products.id
FROM products
INNER JOIN tags ON tags.product_id=products.id
WHERE
products.categoryid IN (1,2,3..) AND
tags.id = 3
ORDER BY products.position, products.id
LIMIT 64;
下面的查询将是
SELECT products.name, products.position, products.id
FROM products
INNER JOIN tags ON tags.product_id=products.id
WHERE
products.categoryid IN (1,2,3..) AND
tags.id = 3
AND (products.position, products.id) > ($1, $2)
ORDER BY products.position, products.id
LIMIT 64;
其中$1
和$2
是前面查询返回的最后一个值。
products (products.position, products.id)
上的索引可以加快查询速度。
【讨论】:
如果位置是布尔字段怎么办?或者,如果我需要使用 products.stock(真/假)。结果不会按升序排序 什么boolean
字段?在你的问题中没有这样的东西。但是很容易在结果或ORDER BY
子句中添加更多列。以上是关于用于 JOIN 查询的 Postgresql 分页性能的主要内容,如果未能解决你的问题,请参考以下文章