公司最近上线了“订单管理系统”,系统内有比较多的分页展示逻辑,所以我单独拿出了点时间,对MySQL的分页做了总结。
提到MySQL分页,我们通常会首先考虑,使用偏移量offset+limit的办法实现。下面以实现目标:
查询订单表order
,并按订单创建时间create_time
,降序排序,每页50
条记录
,为例进行说明。首先我们想到的sql语句如下:
1 | select *from `order` order by `create_time` desc limit 0, 50; |
多次执行该操作,随着页数增加(即:偏移量增大),该查询语句的性能随之下降,耗时比较明显。当并发量上来时,这对MySQL数据库的压力是致命的。以翻页至200页为例,MySQL会查询200 * 50
条记录,最后只返回50
条记录,前面的200 * (50 - 1)
条记录,将会被丢弃。
因为订单的属性很多,所以并无法为每一列建立索引(当然为每一列都建立索引,是简单粗暴的)。优化此类分页查询的一个最简单的办法,尽可能使用索引覆盖扫描,而不是查询所有列,然后在关联返回所需要的列。优化后的SQL语句如下所示:
1 | select *from `order` inner join (select id from `order` order by `create_time` limit desc 10000, 50) as `tmp` using(`id); |
这里MySQL扫描了尽可能少的页面,获取需要访问的记录后,然后再去关联查询,获取了所需的列,该种用法还存在若干类似的变种。
最后,也是我们确定的技术选型。推荐的用法,前端每次查询都传入上次查询记录的max_id,或者min_id传递给我们,然后根据id的索引去优化该操作。(注:只有id列的单调性与目标列的单调性一致时可采用该方案)产品设计上,我们不返回具体的页数,只提供了当前页,前后10页跳页的功能,解决了该问题。
向后翻页
1 | select *from `order` order by `create_time` desc where `id` < min_id limit 50; |
向前跳页
1 | select *from `order` order by `create_time` desc where |
向前翻页
1 | select *from `order` order by `create_time` desc where `id` > max_id limit 50; |
向后跳页
1 | select *from `order` order by `create_time` desc where `id` > max_id limit 450, 50; |
当数据库,采用分库分表,或者中间件时的分页操作,较为复杂,需要根据具体情况确定,本次不做陈述。