功能响应速度优化

功能响应速度优化

Scroll Down

场景描述

CRM系统中的客户列表、售后联系记录、联系记录等模块相对其他功能数据较多。因此根据实际业务要求,将分别插入500W条数据对以上模块进行测试。(下文仅描述联系记录模块)

目标

响应速度2S内

测试准备

为了方便快速的插入数据,这里使用SQL函数进行数据插入。
仅自定义主键uuid和日期。

CREATE DEFINER=`root`@`%` PROCEDURE `插入联系记录`()
BEGIN
 
DECLARE i INT DEFAULT 1;
 
WHILE i<5000000 DO

insert into crm_contactrecord(id,name,customer_id,customer_code,customer_name,seller_id,seller_name,create_time,del_flag,first_office,second_office,contacts_id,contacts_name) 

values (md5(uuid()),'LXJL202103231018055287','097d1ade14564856b4edac7e7e05a2dd','KH2019090913517776','刘虎','224','刘炳俊',now(),'0','209','210','097d1ade14564856b4edac7e7e05a2dd','刘虎');
SET i=i+1; 
END WHILE ; 
commit; 

END

列表显示

不出意外,时间非常长,大概20多秒的样子。
先看下SQL

##分页获取列表前十条记录
select XX from 联系记录表 
left join XX on XX
left join XX on XX
where XX in(select XX from XX where id = xx and deptId = XX OR find_in_set(202,ancestors)) ## 数据权限条件
and XX = XX                                                                                 ## 搜索条件
order by create_time xx xx 
limit 1,10
##获取数据总和数
select count(*) from 联系记录表 where (XXX in (XX,XX))  //这里用的是innodb引擎,不是myisam 这里可以优化为Between替换limit

增加索引

增加NORMAL复合索引。
增加索引后,通过explain查看索引是否执行。
但是并没有,查看程序记录的SQL语句。
原因1:数据类型出现隐式转化。在带入参数时,#
,因为没有单引号,导致char类型转为int类型,导致失效。
原因2:左连接查询到的关联字段编码格式不统一。一张表的字段编码为utf8mb4,另一张表为utf8。
修改后,速度有提升,但是时间还是没有达到要求。

冗余字段

查看sql语句,有些join连接只是为了从母表获取销售ID去关联其他表获取销售名称,像类似的情况可以使用冗余字段来解决。减少不必要的表关联,进一步加快速度。

修改语句

select XX from XX where id = xx and deptId = XX OR find_in_set(202,ancestors)

这个语句在in中,每次都会去select一遍,我觉得这样速度是很慢的,我把这个语句的结果直接拿出来放到in中试了一下,速度有了明显提升。
所以决定在用户登录成功后,获取用户权限去查询本条语句的结果,存在redis中。在需要查询权限时,从redis中直接获取结果放到这个sql语句中去。

        //查询有权限的部门,将sql结果存到redis中
        String[] sql=sysDeptService.selectDeptIdsByDataScope(loginUser.getUser().getDeptId());
        String inSql = StringUtils.join(sql, ",");
        redisCache.deleteObject("inSql:"+loginUser.getUser().getUserId());
	//这里设置失效时间和token失效时间一致
        redisCache.setCacheObject("inSql:"+loginUser.getUser().getUserId(), inSql,SYSTEM.TIME_OUT);

存在的问题

  1. 如果admin修改了角色权限,那么现在在线的用户获取数据的权限就无法实时生效了。
    解决:
    方案1:保存用户角色权限,退出当前在线的所有用户。缺点:用户体验较差,需要重新登录。
 tokenService.delLoginUser(loginUser.getToken());

方案2:在角色管理模块,增加清除缓存操作,将缓存好的角色数据删除掉。在获取权限时,如果redis没有相关数据,就先去查询权限数据再存到redis中。缺点:重新存入redis需要增加耗时。

最终还是选择第二个方案。虽然时间增加,但是是毫秒级的,可以忽略不计,而且修改角色权限也不是频繁操作。

新问题

前端列表展示方式:
image.png
在用户选择尾页或者页码数量较大时,响应时间会非常长。
之前忽略了分页limit。如果页面为5000000,那么limit就是limit 5000000,10 这样速度是非常慢的,因为要排序后找到第五百万页的位置。

方案1:修改前端样式
image.png
参考百度搜索,其实也没有直接显示总条数,而是显示了一个下一页按钮。
我们使用的是elementui,在官方文档中并没有非常符合的示例描述。
image.png

方案2:通过where+索引方式快速定位到当前位置
sql语句:select xx from xx where id > pageNum and del_flag = 0 limit 1 ,10
设置自增id以及逻辑删除,保证id判断大于时数据没有错误。

结果

经过这几次修改,时间已经达到了1.5S左右的响应速度。达到预期的要求了。