使用过滤器对 Active Record 对象进行分页的优化方法是啥?

Posted

技术标签:

【中文标题】使用过滤器对 Active Record 对象进行分页的优化方法是啥?【英文标题】:What is the Optimized way to Paginate Active Record Objects with Filter?使用过滤器对 Active Record 对象进行分页的优化方法是什么? 【发布时间】:2021-10-10 16:21:52 【问题描述】:

我想用我的 Rails API 在分页中显示用户列表,但是在显示用户之前我没有什么限制,我想检查有权访问视图文件的用户,这里是代码:

  def verified_client
    conditions = 
    conditions[:user_name] = fetch_verified_users_with_api_call # returns[user_1,user_2, ....]
    @users = User.where(conditions).where('access NOT LIKE ?', 'admin_%').ordered
    will_paginate(@users, params[:page])
  end

Q1) 当用户尝试获取后续页面(第 2 页、第 3 页.. 第 n 页)时,我是否不必进行 sql 调用?

Q2) 当verified_users 列表返回百万的物品时会发生什么?我怀疑 SQL 会失败

我可以在查询中使用限制和偏移量,但我不知道总结果和页面大小来实现相同的结果我必须再触发一次 SQL 调用来获取计数并编写自己的逻辑来获取页数.

生成的 SQL:

select  *
    from  users
    where  user_name IN (user_1, user_2 .... user_10000)
      AND  (access NOT LIKE 'admin_%')

【问题讨论】:

请提供生成的SQL。 select * from users where user_name IN (user_1, user_2 .... user_10000) AND (access NOT LIKE 'admin_%') 【参考方案1】:

该查询很难优化。它可能基本上完成了每一页的所有工作,并且没有防止这种扫描的好方法。添加这些可能会有所帮助:

INDEX(access)
INDEX(user, access)

我在一个 IN 列表中看到了 70K 个项目,但我没有听说过 1M。到底是怎么回事?说包括哪些用户会更短吗?可以有另一个包含用户列表的表吗? (有时JOIN 比 IN 效果更好,特别是如果您已经运行 Select 来获取列表。)

在构建此查询之前 是否可以将管理员从 IN 列表中过滤掉?那么,

INDEX(user)

可能是相当有益的。

每个用户最多有一行吗?如果是这样,则可以将分页修改为非常高效。这是通过“记住你离开的地方”而不是使用OFFSET 来完成的。更多:http://mysql.rjweb.org/doc.php/pagination

【讨论】:

【参考方案2】:

Q1) 有没有一种方法可以让我在用户尝试时不必进行 sql 调用 获取后续页面(第 2 页,第 3 页.. 第 n 页)?

分页的整个想法是通过返回记录总数的一小部分来加快查询速度。在大多数情况下,第一页的请求数量将大大超过其他页面,因此这很可能是过早优化的情况,弊大于利。

如果确实是一个问题,最好使用 SQL 缓存、ETags 或其他缓存机制来解决 - 而不是一次加载一堆页面。

Q2) 当verified_users 列表返回百万的物品时会发生什么?我怀疑 SQL 会失败

您的数据库或应用程序很可能会在内存不足时缓慢停止,然后崩溃。具体会发生什么取决于您的架构以及您的老板在那一天的脾气暴躁程度。

【讨论】:

有没有针对上述问题的优化解决方案? 这就引出了“优化什么?”的问题。你还没有定义。如果它确实是一个问题并且你知道你实际上在优化什么,我会少担心并解决优化。 重新表述问题:解决问题 1 和问题 2 的设计方法应该是什么 没有单一的设计方法可以解决这些问题。没有明确目标的优化本身就是一种反模式。【参考方案3】:

Q1) 当用户尝试获取后续页面(第 2 页、第 3 页.. 第 n 页)时,我是否不必进行 sql 调用?

您可以获取整个结果集并将其存储在您的应用中。就数据库而言,这不是缓慢或非最佳的。那么包括内存在内的性能就是你应用的问题了。

Q2) 当verified_users 列表返回百万的物品时会发生什么?我怀疑 SQL 会失败

将会发生的情况是所有这些条目都将连接到 SQL 字符串中。可能存在最大 SQL 字符串大小,而一百万个条目可能太多了。

一个可能的解决方案是,如果您有办法识别数据库中经过验证的用户并与该表进行联接。

使用过滤器对 Active Record 对象进行分页的优化方法是什么?

对数据库进行过早优化的三件事是 (1) 使用索引查询而不是表扫描,(2) 避免相关子查询,以及 (3) 减少网络转弯。

确保您有一个可以使用的索引,尤其是对于订单。因此,请确保您知道您要的订单是什么。

如果不是以前缀开头的access 字段,如果您有一个指示管理员用户的字段,您可以使用第一个字段作为该管理员字段和第二个字段作为您订购的内容创建索引。这允许数据库有效地对记录进行排序,在使用offsetlimit 进行分页时尤其重要。

对于网络转弯,您可能希望使用分页而不用担心网络转弯。一个想法是尽可能预取下一页。所以在得到第1页的结果后,查询第2页。保留第2页的结果直到查看,但是当查看时获取第3页的结果。

【讨论】:

以上是关于使用过滤器对 Active Record 对象进行分页的优化方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章

架构模式中的Active Record和Data Mapper

Yii2之Active Record

Active Record:如何读取具有许多其他对象的db对象?

如何通过Active Record计算数据库中的所有对象

Rails 中的 Active Record 和 ORM 有啥区别?

业务逻辑层-Active Record