连接表的慢查询
Posted
技术标签:
【中文标题】连接表的慢查询【英文标题】:Slow query for join tables 【发布时间】:2019-05-21 12:55:03 【问题描述】:我正在尝试优化以下查询:
有两个表,Post
(>100 万条记录)和Category
(大约 10-20 条记录)。
查询应该检索所有category_id
为1 和2 的posts
。对于post_id
和category_id
,连接表posts_categories
有一个索引,并且解释语句确认查询中使用了索引。
但是,查询仍然很慢。看来分组是这里的原因。
感谢一些关于改进它的建议。
Post
.joins(:categories)
.where(categories: id: [1,2] )
.group(:post_id)
.having("COUNT(categories.id) = 2")
SELECT `posts`.* FROM `posts`
INNER JOIN `posts_categories` ON `posts_categories`.`post_id` = `posts`.`id`
INNER JOIN `categories` ON `categories`.`id` = `posts_categories`.`category_id`
WHERE `categories`.`id` IN (1, 2)
GROUP BY `post_id`
HAVING (COUNT(categories.id)=2)
【问题讨论】:
您可以通过在查询代码上调用to_sql
并在问题上显示生成的 SQL 来改进此问题。
请问查询的目的是什么?看起来您想查找属于类别 1 或类别 2 且有两个类别的所有帖子...您是否要找出同时属于类别 1 和类别 2 的帖子?
还有,只是大声思考。 POSTS_CATEGORIES.POST_ID 和 POSTS_CATEGORIES.CATEGORY_ID 上是否有索引?
该查询将返回类别 1 和类别 2 中的帖子。是的,post_id、category_id 和两者的复合索引都有索引。
【参考方案1】:
这可能行不通,但值得一试。
您可以尝试编写子查询来获取类别 ID。
您需要在 PostsCategories 连接表上编写一个简单的 ActiveRecord 模型...
Post
.where(id: PostCategory.where(category_id: [1,2]).select(:post_id))
.joins(:post_categories)
.group(:post_id)
.having("COUNT(post_category.category_id)=2")
所以,这里的优势:
您不再通过联接表联接到大型“类别”表。 子查询允许数据库仅选择具有这些类别 ID 的 PostCategories,从而缩小该字段。【讨论】:
pluck(:post_id)
会不会更好,因为它不会像 select
那样在不构造 ActiveRecord 对象的情况下将数据库查询结果转换为 Ruby 数组?
@MikeHeft 恐怕你搞错了。 pluck
将值作为 ruby 数组返回。 select
使用选择列表中的这些列构造一个 SQL 查询。试试这个:User.select(:id).to_sql
。将其更改为采摘将返回错误。在 where 调用中使用 select 方法会将其转换为带有子查询的单个 SQL 语句。
呸你是对的:P 大声笑。但是,由于它不创建 ActiveRecord 对象,所以不会 pluck 性能更高一些吗?由于 pluck 只返回一个值数组,其中 select 返回一个 ActiveRecord::Relation
@MikeHeft 它将返回 ActiveRecord::Relation
,而不是 AR 对象,除非您调用 all
或可枚举的方法。注意到 POSTS 有超过一百万行,我假设 CATEGORIES_POSTS 不止这些。提取所有这些会导致内存中出现大量数组,并且可能会导致足够长的 SQL 语句溢出(例如,oracle 列表中只能包含 1,000 个项目)。
@MikeHeft 没问题。我很高兴你发现它很有趣。不幸的是,我通过大量痛苦的调整工作学会了这项技术。【参考方案2】:
抱歉,再试一试。
我不确定您是否可以在 ActiveRecord 中编写此代码,并且将其转换为纯 AREL 非常复杂,因此这里有一个解决方法可能会对您有所帮助。
Post.find_by_sql(
"
select * from posts where id in (
SELECT cp1.post_id FROM JOIN categories_posts cp1
INNER JOIN categories_posts cp2 ON cp1.post_id = cp2.post_id
where cp1.category_id = 1 and cp2.category_id = 2
)
"
)
要考虑的另一件事是分页,您可以尝试添加类似这样的内容以仅查看帖子的一部分:
and cp1.post_id between 1 and 10000
(在Oracle上测试,可能有一些语法差异)
【讨论】:
以上是关于连接表的慢查询的主要内容,如果未能解决你的问题,请参考以下文章
PostgreSQL。日志文件中的慢查询在 psql 中很快