页码增加时分页变慢
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
我有一个 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