【前言】最近在优化一个导入功能,导入6w条数据居然花了1天的时间,当时我就惊呆了O.O
后面检查了一下代码,主要的问题在于for循环走创建接口,没用到多线程,所以耗时比较久,这还只是校验数据可用性的阶段,后面还有一个校验唯一性的阶段,这个阶段需要21个属性均不重复来校验唯一性(原来有25个属性,有几个属性的校验是多余的,被优化了),旧表没有索引且所有属性列均没有设置非空和默认值,导致sql里有一堆的判空处理
<choose>
<when test="attr == null or attr == ''">
and (attr is null or attr = '')
</when>
<otherwise>
and attr = #{attr}
</otherwise>
</choose>
作为一只有代码洁癖的程序猿,下巴都给我惊掉了,然后这样的判断多达9处
好在这张表只有6条数据,不然1天估计跑得够呛
由于优化的项目没集成消息队列,且对redis缓存的使用等于零(单机得不能再单机的单机应用),这时候我想去依靠这2个中间件几乎是不可能的事情,因为很多表都没做缓存处理,如果我用Redis的话就还需要去维护缓存,由于工期比较赶(懒),所以直接全部拉到JVM内存里操作
【缺点总结】
1. for循环走创建接口,即使只有6w数据,性能依然极其底下
2. 数据表属性列没设置非空和默认值,导致需要is null 和 空字符判断,如果设置默认值为空字符串,则只需要判断 attr = '' 即可
3. 多属性校验过程太过粗糙,且未排除多余属性,比如 [A,B,C,D.....]属性,通过A就可以确定对象时,又传入B,C,D....属性进行确认,这操作没必要
【推荐解决方案】
① 直接丢消息队列/redis队列,慢慢消费
② 多线程消费
本文主要介绍多属性唯一性校验的解决方案
下面开始我解决这个问题的过程
当时看到sql那边拼接25个属性校验,我立马想到的就是过滤冗余判断属性,并计算这21个属性的MD5值来校验唯一性,因为当某一个属性变动时,这个对象就是新的对象,也就是MD5值也会变成新的
一开始想到的是用mysql的json_object来拼接对象,当我拼完后并且计算出MD5后感觉一切都很顺利,但让我用Java写一遍计算MD5过程后却发现计算得出的MD5值不一样
检查后发现json_object是根据max_sort_length进行排序的,即先比较字符串长度,当长度一样时再比较字符串大小
OK,那我改用TreeMap来填充可以吧,然后重写Comparator比较器
打印出的字符串比较后,可以的,这时候顺序是对的
但是!!!打印的MD5还是不一致! 摔碗.gif
原来json_object转字符串后会在key:value的冒号后面多个空格,然后key1和key2之间还有个空格
而Java的JSON工具转成字符串后都会把多余的空格去掉 嗯,我已经不知道摔了几只碗了
一开始解决时,用了mysql的REPLACE函数,但是要真对字符串和数字,所以至少得4次使用REPLACE函数
REPLATE(REPLACE(REPLACE(REPLACE(json, '": "', '":"'), '": ', '":'), '", "', '","')))
是不是觉得头皮发麻?所以我放弃用mysql处理,转用Java处理拼接空格,而不是mysql去除空格
但是这个方法还是不够好,又想到一个更好的
mysql处理
用concat去拼接字符串, 格式: key1=value1,key2=value2,key3=value3
SELECT MD5(CONCAT('key1=', value1, ',key2=', value2, ',key3=', value3))
java处理
用Arrays.asList('key1', 'key2', 'key3')存储顺序,然后将对象转Map<String, Object>, 直接遍历list去封装字符串就行了
Map<String, Object> map = new HashMap<>();
map.put("key1", value1);
map.put("key2", value2);
map.put("key3", value3);
List<String> list = Arrays.asList("key1", "key2", "key3");
String str = list.stream().map(key -> {
Object value = map.get(key);
return key + "=" + value;
}).collect(Collectors.joining(","));
// 我这边用的hutool工具的MD5工具,可以自行选择
String md5 = MD5.create().digestHex(str);
数据库表只要新增一个MD5属性用来存储并加上索引就行了,第一次初始化之后,后续校验唯一只需要在代码里拼接好字符串后再进行MD5计算之后根据md5值查询数据库就可以快速判断是否已存在
注意事项
value的值要注意默认值的处理
当字段变动时(字段名修改,删除字段,增加字段),MD5的值得重新计算
因为这边的数据只有6w,所以用的32位MD5
如果怕重复的话,可以参考我的另一篇文章 千万级别数据去重思路
核心思想就是拼接字符串,然后做加密,MD5只是其中一种方式
如果帮到你,请点个赞吧 O(∩_∩)O~