连接表的慢查询

Posted

技术标签:

【中文标题】连接表的慢查询【英文标题】:Slow query for join tables 【发布时间】:2019-05-21 12:55:03 【问题描述】:

我正在尝试优化以下查询:

有两个表,Post(>100 万条记录)和Category(大约 10-20 条记录)。

查询应该检索所有category_id 为1 和2 的posts。对于post_idcategory_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上测试,可能有一些语法差异)

【讨论】:

以上是关于连接表的慢查询的主要内容,如果未能解决你的问题,请参考以下文章

mysql中的慢查询会不会影响速度

PostgreSQL。日志文件中的慢查询在 psql 中很快

Redis的慢查询日志

Django 慢查询:将 django 过滤语句连接到数据库日志中的慢查询

复合主键上的慢连接

MySQL 的慢查询日志