【问题标题】:Pagination getting slower while page number increasing页码增加时分页变慢
【发布时间】:2022-01-23 02:14:06
【问题描述】:

我有一张这样的桌子。

parent   VARCHAR PK
priority INT     PK
child    VARCHAR

现在我需要选择/计数,对于春季分页,那些孩子超过指定数量的人。

我的问题来了。

-- SELECT
SELECT
  parent,
  GROUP_CONCAT(child ORDER BY priority SEPARATOR 0x1D) AS concatenated_children,
  COUNT(child) AS child_count
FROM mytable
GROUP BY parent
HAVING child_count >= ?1
ORDER BY parent ASC
LIMIT 2048,?   -- GETTING SLOWER when the OFFSET is increases
-- COUNT
SELECT COUNT(wrk.parent)
FROM (SELECT parent, COUNT(child) AS child_count
      FROM mytable
      GROUP BY parent
      HAVING child_count >= ?1) AS wrk

?1 部分是常量(8),偏移部分(,?)是每分页增加。

问题是随着页码的增加,选择查询变慢了。

?size=2048,page=0   took 60 ms
?size=2048,page=100 took 2646 ms

我该如何解决这个问题?

CREATE TABLE `mytable` (
  `parent` varchar(100) NOT NULL,
  `child` varchar(255) NOT NULL,
  `priority` int(11) NOT NULL,
  PRIMARY KEY (`parent`,`priority`)
)
parent child           priority
---------------------------------
NIKE   Air Max         1
NIKE   Air Jordan      2
WATCH  Patek Philippe  1
WATCH  Rolex           2

标准

Select all parents (and its children)
which each has at least some number children, say 8.

【问题讨论】:

  • SHOW CREATE TABLE table_name; 显示了有关表结构的更多信息,因此如果您可以发布它们会有所帮助。如果您能提供查询的执行计划,也会有所帮助。
  • @FanoFN 已更新。
  • "PRIMARY KEY (parent,priority)" -- 这允许一个父母多次出现;真的是这样吗?
  • @RickJames 是的,我相信。同一个parent 中的那些孩子(child)应该按唯一的priority 排序。
  • @JinKwon - 如果您通过 Offset 转储整个表格,则需要读取 100*51“页”。通过“记住你离开的地方”倾倒只需要 100 页。看我的回答。

标签: mysql sql pagination


【解决方案1】:

如前所述,OFFSET 必须获取并折腾所需行之前的所有行。

如果您可以将查询编码为“记住您上次离开的位置”,则有一种比使用OFFSET 更快的方法。

这是一个 MySQL 特定的讨论: http://mysql.rjweb.org/doc.php/pagination

【讨论】:

    【解决方案2】:

    速度变慢是因为OFFSET 的工作方式:它获取所有数据,然后才删除偏移量之前的部分。对于您的情况,这意味着分组不仅会发生在当前页面上,还会发生在所有以前的页面上。

    解决此类问题的标准技巧是使用Keyset Pagination。获取页面时,您需要记住它的最后一个parent。然后为了获取下一页,您将查询与

    WHERE parent > YOUR_LAST_PARENT
    

    子句并且没有OFFSET。 RDBMS 将看到 parent 已编入索引,并快速将索引导航到正确的 parent

    完整查询

      SELECT parent,
             GROUP_CONCAT(child ORDER BY priority
                                SEPARATOR 0x1D) AS concatenated_children,
             COUNT(child) AS child_count
        FROM mytable
       WHERE parent > ?1
    GROUP BY parent
      HAVING child_count >= ?2
    ORDER BY parent
       LIMIT 2048
    

    查询第一页时,您需要在不使用WHERE 子句的情况下运行此查询。

    您的 URL 参数现在看起来像

    ?size=2048,parent=PARENTXXX
    

    当然,您将不得不放弃 Spring Data JPA 内置分页。也不能立即跳转到特定页面,您基本上仅限于下一页/上一页(只要您记得上一页parent)。

    顺便说一句,在试验这个时,我观察到 Spring Data JPA 分页的主要问题是它必须 COUNT 所有行。对于 InnoDB,仅此一项就比获取页面花费更多时间。

    编辑。答案的第一个版本建议将parent 的条件添加到HAVING 子句中。正如下面的 cmets 所述,它不起作用(我测试过它不起作用)。

    【讨论】:

    • 您的链接指向非 MySQL 代码。大多数情况下,MySQL 不会在HAVING 子句中使用索引;为时已晚。
    • 似乎仍然需要 HAVING -- 因为 child_count 是一个聚合。 OFFSET 消失了,但限制仍然存在。
    猜你喜欢
    • 1970-01-01
    • 2015-12-22
    • 1970-01-01
    • 1970-01-01
    • 2023-04-10
    • 2018-07-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多