优化查询性能 | 搜索标签

67 阅读2分钟

背景

用户表User有一个字段Tags,用于描述用户的个人特点/能力。

业务需求:

  1. 查询满足标签列表的所有用户
  2. 请求体中的标签列表是 JSON字符串

分析

实现方式

  1. SQL查询(实现简单,可以通过拆分查询进一步优化)
  2. 内存查询(灵活,可以通过并发进一步优化)

根据查询效率选择不同的查询方法:

建议通过实际测试来分析哪种查询比较快,数据量大的时候验证效果更明显!

  • 如果参数可以分析,根据用户的参数去选择查询方式,比如标签数(标签数 > 个临界值时,使用 某个方法查询更快)
    • 考虑使用不同的连接池
    • 考虑使用SQL查询、内存查询时间
  • 如果参数不可分析,并且数据库连接足够、内存空间足够,可以并发同时查询,谁先返回结果用谁
  • 还可以SQL查询与内存查询相结合,比如先用SQL过滤掉部分条件(例如:tag)

实现

SQL查询

    /**
     * 根据标签搜索用户
     *
     * @param tagNameList 标签列表数组( 已通过GSON 将 JSON -> List )
     * @return 脱敏后的用户列表
     */
    public List<User> searchUsersByTagsToSql(List<String> tagNameList) {
//        long startTime = System.currentTimeMillis();
        // 参数校验
        if (CollectionUtils.isEmpty(tagNameList)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }

        // 拼接 and 查询
        // 封装条件构造器
        QueryWrapper<User> userQueryWapper = new QueryWrapper<>();
        for (String tagName : tagNameList) {
            userQueryWapper = userQueryWapper.like("tags", tagName);
        }
        // 查询
        List<User> userList = userMapper.selectList(userQueryWapper);
        // 脱敏
        List<User> resultList = userList.stream().map(this::getSafetyUser).collect(Collectors.toList());
//        log.info( " sql query time = "+ (System.currentTimeMillis()-startTime) );
        return resultList;
    }
  • 拼接 and 查询:tags LIKE ? AND tags LIKE ? AND tags LIKE ?
  • this::getSafetyUser:
userList.forEach( user ->{
    getSafetyUser(user);
});
  • userList.stream().map(this::getSafetyUser).collect(Collectors.toList()):

内存查询

先全量将数据加载内存中,再根据条件查询

public List<User> searchUsersByTagsToMemory(List<String> tagNameList) {
//        long startTime = System.currentTimeMillis();
        // 参数校验
        if (CollectionUtils.isEmpty(tagNameList)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        // 全量查询
        QueryWrapper<User> queryWrap = new QueryWrapper<>();
        List<User> userList = userMapper.selectList(queryWrap);

        Gson gson = new Gson();
        // Json转Java对象 (数据库中存储的Tags是Json格式,需要转为Java对象才能操作)
//        for (User user : userList) {
//            String tagsJson = user.getTags();
//            // Json 反序列化为 Java对象
//            Set<String> tempTagsNameSet = gson.fromJson(tagsJson, new TypeToken<Set<String>>(){}.getType());
//            for( String tagName : tagNameList){
//                if( tempTagsNameSet.contains(tagName)){
//                    return false;
//                }
//            }
//            return true;
//        }
        // 使用语法塘代替 foreach
         userList.stream().filter((user) -> {
            String tagsJson = user.getTags();
            if( StringUtils.isBlank(tagsJson)){
                return false;
            }
            // Json 反序列化为 Java对象
            Set<String> tempTagsNameSet = gson.fromJson(tagsJson, new TypeToken<Set<String>>() {
            }.getType());
            for (String tagName : tagNameList) {
                if (!tempTagsNameSet.contains(tagName)) {
                    return false;
                }
            }
            return true;
        }).map(this::getSafetyUser).collect(Collectors.toList());
//        log.info( " memory query time = "+ (System.currentTimeMillis()-startTime) );

        return userList;
    }

解析JSON字符串:

序列化:Java对象 转成 json

反序列化:json 转称 Java对象

常用java json序列化库:

  • gson(google)

  • astjson (阿里,速度快,漏洞多)

  • jackson

  • kryo

测试

注意:测试查询速度时,需要考虑数据库首次连接使用的时间,避免影响对不同方式的性能比较。 解决:可以先使用一次查询,避免这种情况的发生。