页码增加时分页变慢

Pagination getting slower while page number increasing

我有一个 table 看起来像这样。

parent   VARCHAR PK
priority INT     PK
child    VARCHAR

现在我需要SELECT/COUNT,为了Spring分页,那些children超过指定数量的。

我的问题来了。

-- 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.

速度变慢是因为 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 内置分页。也不能立即跳转到特定页面,您基本上仅限于 next/previous 页面(只要您记得上一页 parent)。

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

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

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

如果您可以将查询编码为“记住您离开的地方”,则有一种比使用 OFFSET.

快得多的方法

这里是 MySQL 的具体讨论: http://mysql.rjweb.org/doc.php/pagination