业务场景:基于 MySQL 数据库,从有约 600 万条记录的表 t_content 中先按照 date 从小到大排序,然后获取 name、date、detail 字段
偏移分页
SELECT id, name, date, detail FROM t_content ORDER BY date LIMIT 5000, 1000;
SELECT id, name, date, detail FROM t_content ORDER BY date LIMIT 5000000, 1000;
深分页会严重影响查询性能
偏移分页无索引:若 date 列无索引,则 SQL 会对约 600 万条数据进行排序,然后丢弃前 5000000 条数据,返回随后的 1000 条数据
偏移分页有有索引:若 date 列有索引,则 SQL 会根据遍历索引丢弃前 5000000 条数据, 返回随后的 1000 条数据
整体上,偏移分页有索引比偏移分页无索引节省排序资源消耗,但仍需要从头遍历,丢弃前面大部分数据,保留后面小部分数据
游标分页
第一次请求记录最大id为cursor_id
SELECT name, date, detail FROM t_content ORDER BY date LIMIT 1000;
第二次以及之后请求时使用cursor作为条件查询,并记录本次查询的最大date为cursor
SELECT name, date, detail FROM t_content WHERE date > cursor ORDER BY date LIMIT 1000;
游标分页无索引:若 date 列无索引,先根据 date > cursor 条件过滤数据,然后对剩余数据进行排序,最后直接返回前 1000 条数据即可。
游标分页有索引:若 date 列有索引,先根据 date > cursor 条件过滤数据,由于排序命中索引,随后可以快速返回前 1000 条数据。
通常会有排序条件,然后排序条件全部或者部分必须构成唯一索引来作为 cursor,因为过滤必须是精确的,无排序条件时可使用自增 id(满足有序和唯一),上述例子中,要实现游标分页效果,必须保证 date 唯一。
游标分页无法直接获取指定页码数据,且必须从前往后获取数据。
# 元组排序
where (product_id, open_date) > (9345345, “2020-02-02”)
# 等价于
where product_id >= 9345345 and open_date > “2020-02-02”
# 就像 56 > 54 和 5 >= 5 and 6 > 4 一样
分页漏数据问题
数据库排序不稳定导致返回数据泄漏
即使数据不变,仅仅使用 limit 并不能保证每一次同样参数获取到的数据相同,limit 并不保证数据相同
SELECT id, name, date, detail FROM t_content limit 5000, 1000;
所以一定要使用 order by 进行排序,且如果 order by 后面的键也不能唯一确认一行数据的话,也不能保证顺序,故 order by 后面一定跟一个唯一键,例如主键
数据被删除导致漏数据
SELECT * FROM order WHERE ... ORDER BY id ASC LIMIT 3,3;
就需要游标式分页,见上文。
通常情况,列表展示页面使用分页,漏数据用户也感知不到,若是分页后台处理数据,数据就不能重复也不能遗漏,例如广告中的报表API必须做到不重不漏。
如果你只需要结果集中的某几行,那么建议使用 limit。这样这样的话可以避免抓取全部结果集,然后再丢弃那些你不要的行。
对于 order by 查询,带或者不带 limit 可能返回行的顺序是不一样的。
如果 limit row_count 与 order by 一起使用,那么在找到第一个 row_count 就停止排序,直接返回。
如果 order by 列有相同的值,那么 MySQL 可以自由地以任何顺序返回这些行。换言之,只要 order by 列的值不重复,就可以保证返回的顺序。
可以在 order by 子句中包含附加列,以使顺序具有确定性。
数据新增导致漏数据
如果主键不自增,那么无法处理,主键自增就不会出现该情况
其他
Elasticsearch 中的 scroll 也是游标分页。