redis多字段分页排序

Posted xixingzhe2

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了redis多字段分页排序相关的知识,希望对你有一定的参考价值。

1、redis有序集合

sorted set 是在 Sets 的基础上增加了分数设置作为排序依据,所以除了具备 Sets 的特性外,还可以进行排序。它提供了一个 score 属性,正好可以用来做排序依据。

2、单字段排序

用户类

@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserDTO 
    private Long id;
    private String name;
    /**
     * 创建时间
     *
     * @author: ybw
     * @date: 2023/3/24
     **/
    private LocalDateTime createDateTime;
    /**
     * 积分
     *
     * @author: ybw
     * @date: 2023/3/24
     **/
    private Long point;

初始化数据,根据createDateTime排序

public void init() 
    for (long i = 0L; i < 1000L; i++) 
        UserDTO userDTO = new UserDTO(i, "zhangsan" + i, LocalDateTime.now().plusDays(i%500),i);
        // 按时间排序
        redisTemplate.opsForZSet().add(key, userDTO, userDTO.getCreateDateTime().toInstant(ZoneOffset.of("+8")).toEpochMilli());
    

分页查询

public void page() 
    //页码
    int pageNum = 1;
    //一页条数
    int pageSize = 20;
    int start = (pageNum - 1) * pageSize;
    int end = start + pageSize - 1;
    // 时间从小到大排序
    Set<UserDTO> set = redisTemplate.opsForZSet().range(key, start, end);
    log.info("set:", JSON.toJSONString(set));

打印日志如下,可以看到createDateTime升序返回的:

[
    "createDateTime": "2023-03-24T11:01:33.470",
    "id": 0,
    "name": "zhangsan0",
    "point": 0
, 
    "createDateTime": "2023-03-24T11:01:36.343",
    "id": 500,
    "name": "zhangsan500",
    "point": 500
, 
    "createDateTime": "2023-03-25T11:01:36.143",
    "id": 1,
    "name": "zhangsan1",
    "point": 1
, 
    "createDateTime": "2023-03-25T11:01:36.343",
    "id": 501,
    "name": "zhangsan501",
    "point": 501
, 
    "createDateTime": "2023-03-26T11:01:36.144",
    "id": 2,
    "name": "zhangsan2",
    "point": 2
, 
    "createDateTime": "2023-03-26T11:01:36.343",
    "id": 502,
    "name": "zhangsan502",
    "point": 502
, 
    "createDateTime": "2023-03-27T11:01:36.146",
    "id": 3,
    "name": "zhangsan3",
    "point": 3
, 
    "createDateTime": "2023-03-27T11:01:36.344",
    "id": 503,
    "name": "zhangsan503",
    "point": 503
, 
    "createDateTime": "2023-03-28T11:01:36.146",
    "id": 4,
    "name": "zhangsan4",
    "point": 4
, 
    "createDateTime": "2023-03-28T11:01:36.344",
    "id": 504,
    "name": "zhangsan504",
    "point": 504
, 
    "createDateTime": "2023-03-29T11:01:36.147",
    "id": 5,
    "name": "zhangsan5",
    "point": 5
, 
    "createDateTime": "2023-03-29T11:01:36.344",
    "id": 505,
    "name": "zhangsan505",
    "point": 505
, 
    "createDateTime": "2023-03-30T11:01:36.148",
    "id": 6,
    "name": "zhangsan6",
    "point": 6
, 
    "createDateTime": "2023-03-30T11:01:36.344",
    "id": 506,
    "name": "zhangsan506",
    "point": 506
, 
    "createDateTime": "2023-03-31T11:01:36.148",
    "id": 7,
    "name": "zhangsan7",
    "point": 7
, 
    "createDateTime": "2023-03-31T11:01:36.344",
    "id": 507,
    "name": "zhangsan507",
    "point": 507
, 
    "createDateTime": "2023-04-01T11:01:36.150",
    "id": 8,
    "name": "zhangsan8",
    "point": 8
, 
    "createDateTime": "2023-04-01T11:01:36.345",
    "id": 508,
    "name": "zhangsan508",
    "point": 508
, 
    "createDateTime": "2023-04-02T11:01:36.152",
    "id": 9,
    "name": "zhangsan9",
    "point": 9
, 
    "createDateTime": "2023-04-02T11:01:36.345",
    "id": 509,
    "name": "zhangsan509",
    "point": 509
]

3、多字段排序

sorted set可以通过score进行单字段排序,如果是多字段排序,如下面的sql,如何实现呢?

select * from user order by score desc,name desc;

3.1 按创建时间升序、积分降序

UserDTO不变、page(分页查询)不变,修改初始化代码。

限制条件:用户积分不能超过1000。代码实现

  • createDateTime在高位。

  • point在低位,用1000-point达到降序的效果。

public void init() 
    for (long i = 0L; i < 1000L; i++) 
        UserDTO userDTO = new UserDTO(i, "zhangsan" + i, LocalDateTime.now().plusDays(i % 500).with(LocalTime.MIN), i);
        //积分不超过1000
        Long pointLimit=1000L;
        // 按时间升序、积分降序
        double score = userDTO.getCreateDateTime().toInstant(ZoneOffset.of("+8")).toEpochMilli()*pointLimit + (pointLimit - userDTO.getPoint());
        redisTemplate.opsForZSet().add(key, userDTO, score);
    

分页查询日志,在createDateTime相同的情况下,按point降序排列。

[
    "createDateTime": "2023-03-24T00:00:00",
    "id": 500,
    "name": "zhangsan500",
    "point": 500
, 
    "createDateTime": "2023-03-24T00:00:00",
    "id": 0,
    "name": "zhangsan0",
    "point": 0
, 
    "createDateTime": "2023-03-25T00:00:00",
    "id": 501,
    "name": "zhangsan501",
    "point": 501
, 
    "createDateTime": "2023-03-25T00:00:00",
    "id": 1,
    "name": "zhangsan1",
    "point": 1
, 
    "createDateTime": "2023-03-26T00:00:00",
    "id": 502,
    "name": "zhangsan502",
    "point": 502
, 
    "createDateTime": "2023-03-26T00:00:00",
    "id": 2,
    "name": "zhangsan2",
    "point": 2
, 
    "createDateTime": "2023-03-27T00:00:00",
    "id": 503,
    "name": "zhangsan503",
    "point": 503
, 
    "createDateTime": "2023-03-27T00:00:00",
    "id": 3,
    "name": "zhangsan3",
    "point": 3
, 
    "createDateTime": "2023-03-28T00:00:00",
    "id": 504,
    "name": "zhangsan504",
    "point": 504
, 
    "createDateTime": "2023-03-28T00:00:00",
    "id": 4,
    "name": "zhangsan4",
    "point": 4
, 
    "createDateTime": "2023-03-29T00:00:00",
    "id": 505,
    "name": "zhangsan505",
    "point": 505
, 
    "createDateTime": "2023-03-29T00:00:00",
    "id": 5,
    "name": "zhangsan5",
    "point": 5
, 
    "createDateTime": "2023-03-30T00:00:00",
    "id": 506,
    "name": "zhangsan506",
    "point": 506
, 
    "createDateTime": "2023-03-30T00:00:00",
    "id": 6,
    "name": "zhangsan6",
    "point": 6
, 
    "createDateTime": "2023-03-31T00:00:00",
    "id": 507,
    "name": "zhangsan507",
    "point": 507
, 
    "createDateTime": "2023-03-31T00:00:00",
    "id": 7,
    "name": "zhangsan7",
    "point": 7
, 
    "createDateTime": "2023-04-01T00:00:00",
    "id": 508,
    "name": "zhangsan508",
    "point": 508
, 
    "createDateTime": "2023-04-01T00:00:00",
    "id": 8,
    "name": "zhangsan8",
    "point": 8
, 
    "createDateTime": "2023-04-02T00:00:00",
    "id": 509,
    "name": "zhangsan509",
    "point": 509
, 
    "createDateTime": "2023-04-02T00:00:00",
    "id": 9,
    "name": "zhangsan9",
    "point": 9
]

4、源代码

https://gitee.com/xixingzhe2/share/tree/master/redis/redis-page

MySQL深分页 + 多字段排序场景的优化方案三百万级数据量

需求背景

目前产品需要针对一个大范围地区内的所有用户做排行榜功能,且这个排行榜有几个比较蛋疼的附加需求:

排行榜需要全量展示所有用户,且做分页展示(大坑💥)

排行榜有4种排序条件,且每个排序条件都是单独的。例如:用户的应用A下载数、应用B下载数、应用C下载数、应用D下载数(产品不期望把所有的数据整合成一块进行排名)

历史代码背景

其实这个需求已经够扯了,雪上加霜的是,以前的开发者在开发排行榜的时候,由于需求背景原因,采用了多表join的方式来进行查询。

这是什么意思呢?这里详细说一下:

  • 假设有一张表C,就是排行榜的单表数;

  • 目前无法直接从表C中拿到排行榜的所有所需字段。

听说是需求原因,别无他法

导致了开发者在MyBatis层面通过多表join的方式补充了所需字段;

最后SQL就是:

table A left join table B left join table C

以上方式进行多表join,先不说分页、排序等性能问题。单纯表A join 表B都已经耗时3秒(表A数据量250W、表B数据量300W)

优化点一:多表join优化

针对多表join的问题,必须想尽办法把多表join的查询操作改为单表查询,否则多表join随着数据量增加,后期性能不敢想象

优化点二:优化SQL,避免深分页所带来的问题

select * from table A t1 join (
 select id from table A 
 order by indexA des 
 limit 20000, 20
) t2 on t1.id = t2.id

这里通过子查询的方式,且限制分页的页数(注意,虽然pageSize = 20000,MySQL会扫描前20000个数据,然后再从20001开始拿数据,再丢弃前20000条数据,因此还是会顺序扫前面的20000条数据,而不是跳到20001开始扫描的。这里可以网上查资料学习下)

此SQL还有优化空间,就是在临时的子表中补充上where条件,就可以直接筛选掉大部分无效数据

select * from table A t1 join (
 select id from table A  
 where index_value > 100000 
 order by indexA des 
 limit 20000, 20
) t2 on t1.id = t2.id
抛出一个问题

MySQL的in和join,谁的性能更好?

在本次开发过程中,in的方式指定查询大量数据,发现DB查询超时了;只有使用join的方式才能查到数据,这是为什么?

优化点三:深分页,设定阈值合理倒序查询

其实深分页最主要的问题就是 limit m, n 这个偏移量的问题;如果正序数,拿最后一页数据,相当于扫描前m个数据,再从m+1开始拿到m+n;

仔细想想,这个地方,如果正序拿最后一页,那不如我直接倒序拿第一页?这样的话,就规避了深分页问题,这个一般会设定一个阈值,超过阈值就进行正序 / 倒序

优化点四:排序字段单独建立索引

其实针对排序字段,一般会补充索引来进行优化,因此多字段排序的话,尽量让每个排序字段都单独设置一个索引,因为索引已经帮我们做好排序了。

这里要注意一个点,尽量不要接多字段同时排序的需求,这种情况下索引的设计将会十分复杂

优化点五:单表拆分

因为表C有多个排序字段,且还有各种where条件筛选,此时如果建立联合索引来解决的话,因为需要满足 最左匹配原则,此时联合索引的数量将会很大,届时索引树也会十分复杂。还好排行榜是读多写少的表数据,否则性能堪忧;

此时其实更建议进行单表的拆分,让每一个表所负责的职责更加明确;因为以前的表C,相当于就是把多个排行榜冗余在一个单表中了,这时候表C的压力是很大的。因此单表拆分,此时针对单表的排序字段建立对应的索引,且单表职责更加单一;

单表拆分方案:

  • 查询某个字段的排序数据时,在MyBatis层面,根据排序字段,指定查询排序字段所对应的单表。

  • 单表拆分后,需要合理创建索引

作者:ADAMs.

来源:blog.csdn.net/qq_43097201/article/

details/128773909

推荐

Java面试题宝典

技术内卷群,一起来学习!!

PS:因为公众号平台更改了推送规则,如果不想错过内容,记得读完点一下“在看”,加个“星标”,这样每次新文章推送才会第一时间出现在你的订阅列表里。点“在看”支持我们吧!

以上是关于redis多字段分页排序的主要内容,如果未能解决你的问题,请参考以下文章

php 分页查询怎么redis缓存

redis

MongoDB分页获取数据排序阶段缓存溢出问题

简介redis之集合类型数据

初学redis分页缓存方法实现

因在缓存对象中增加字段,导致Redis出现反序列化失败的问题