我有一个REST服务,它根据当前页面和每页结果返回数据库表中的行。
当不过滤结果时,它很容易做到,我只做一个SELECT WHERE id> =(page - 1)* perPage + 1和LIMIT to perPage。
问题在于尝试对过滤结果使用分页,例如如果我选择仅过滤行WHERE type = someType。
在这种情况下,第一页的第一个匹配可以在id 7中开始,最后一个可以在id 5046中。然后第二页的第一个匹配可以从7302开始并在12430结束,依此类推。
对于过滤结果的第一页,我只能从id 1和LIMIT到perPage开始,但对于第二页等,我需要知道最后一个匹配行的索引。上一页,甚至更好 - 当前页面中第一个匹配的行,或其他一些指示。
我该如何有效地做到这一点?我需要能够在具有数百万行的表上执行此操作,因此显然获取所有行并从中获取它不是一种选择。
这个想法是这样的:
SELECT ... FROM ... WHERE filterKey = filterValue AND id >= id_of_first_match_in_current_page
id_of_first_match_in_current_page是个谜。
答案 0 :(得分:3)
您无法知道给定网页上的第一个ID是什么,因为ID号不一定是连续的。换句话说,序列中可能存在间隙,因此100行的第五页上的行不必从id 500开始。例如,它可以从id 527开始,它是不可能知道的。
另一种说法是: id是一个值,而不是一个行号。
如果您的客户端按升序推进页面,则一种可能的解决方案是每个REST请求获取数据,记录该页面上的最大 id值,然后在 next < / em> REST请求,因此它会查询更大的id值。
SELECT ... FROM ... WHERE filterKey = filterValue
AND id > id_of_last_match_of_previous_page
但是,如果您的REST请求可以获取任何随机页面,则此解决方案无法正常工作。这取决于已经取得前一页。
另一种解决方案是使用LIMIT <x> OFFSET <y>
语法。这允许您请求任意页面。 LIMIT <y>, <x>
的工作原理相同,但由于某些原因,x和y在两种不同的语法形式中相反,所以请记住这一点。
当您请求结果中有多页的页面时,使用LIMIT...OFFSET
非常有效。假设您要求第5,000页。 MySQL必须在服务器端生成5,000页的结果,然后丢弃4,999个结果并返回结果中的最后一页。抱歉,但这是如何运作的。
重新评论:
您必须了解WHERE
在行中值上应用条件,但页面由行的位置定义。这是确定行的两种不同方法!
如果您的列保证为行号,则可以将该值用作行位置。您甚至可以在其上添加索引,或将其用作主键。
但是主键值可能会更改,并且可能不会是连续的,例如,如果更新或删除行,或者回滚某些事务,等等。重新编号主键值是个坏主意,因为其他表或外部数据可能会引用主键值。
因此,您可以添加另一列不主键,但只能添加行号。
ALTER TABLE MyTable ADD COLUMN row_number BIGINT UNSIGNED, ADD KEY (row_number);
然后在需要重新编号行时填充值。
SET @row := 0;
UPDATE MyTable SET row_number = (@row := @row + 1) ORDER BY id;
例如,如果删除某些行,则必须重新对行进行编号。根据桌子的大小,经常这样做效率不高。
此外,新插入无法在不锁定表的情况下创建正确的行号值。这对于防止竞争条件是必要的。
如果您保证row_number
是一系列连续值,那么它既是值又是行位置,因此您可以将其用于任何页面的高性能索引查找行。
SELECT * FROM MyTable WHERE row_number BETWEEN 401 AND 500;
至少在下次通过删除或新插入对行号序列产生疑问之前。
答案 1 :(得分:1)
您出于错误的目的使用ID列。 ID是记录的标识符,而不是任何给定结果集的记录的序列号。
LIMIT
关键字扩展到基本分页。如果您只想要前10条记录,您可以执行以下操作:
LIMIT 10
要分页,如果你想要第二个 10条记录,你可以这样做:
LIMIT 10,10
之后的10:
LIMIT 20,10
等等。
LIMIT
子句独立于WHERE
子句。使用WHERE
过滤结果,使用LIMIT
对其进行分页。