java-性能优化篇
因为一次公司倒闭,感悟到人生不容易,选择以物联网做副业,从事物联网卡代理,希望认识更多好朋友,合作共赢.
(当你身上有了担子,你就会发现,收入来源太单一,会使你没有安全感!)
公司项目已经完成阶段性上线,目前正在对残留的一些性能问题进行优化,今天针对优化过程中的一系列流程进行整理,希望能帮助到各位。我们单纯的先从软件层面出发(硬件的说实话,不是很good at)
第一步:分析性能问题
首先,可以依据类似听云这类全链路日志,可以看到整个接口的调用链路以及响应时间。每个sql语句的耗时以及后续api的调用时长。
第二步:分块优化
大多数情况下,性能问题是由于一些慢sql导致的,那么接下来,面试时问到的一些关于sql优化的方案就可以派上用场了。
SQL层面
-
从建表出发
1.1 CHAR是可变长字符串,VARCHAR是固定大小,字符类型尽量根据实际预存值大小设置,否则会浪费空间;
1.2 尽量避免出现null值,给缺省值(默认值);
1.3 能用int类型的不要用varchar或者char类型,否则mysql比较相等时,会默认每个字符依次匹配;
-
从索引的角度出发
2.1 是否添加必要索引 ?
所谓必要列的索引,指的是不会频繁更新的,与其他表关联的,小的字段;2.2 ** 执行计划中查看是否命中索引?**
有时存在sql优化器自动选取较优索引,如果不是实际最优,我们可以用**use index(index_name)**指定使用某个索引;2.3 如果存在复合索引,那么请注意where后面的过滤条件的先后顺序是否遵循索引的最左原则;
-
从sql语句出发
3.1 避免大数据量in查询(一般超过200,索引就显得力不从心了);
3.2 能用关联查询的尽量不用子查询(子查询会生成临时表,表的创建和销毁有性能开销);
3.3 如果存在类似union查询两端都需要基于某张表做各自的统计查询,这个时候**CTE(with as)**语句就很nice;
with temp_table as( select id, name ,age from user where schoole_id ="" ) select * from temp_table; 通过with as创建公用表表达式,后续的sql都可以直接用已经生成(过滤后)的公用表做操作。
3.4 如果join两边的表数据量特别大,并且过滤条件能有效的过滤掉很大一部分数据,可以尝试先子查询过滤在join(理论上)
3.5 sql尽量避免回表。在innodb聚集索引的情况下,主键索引的叶子结点存储实际数据,非主键索引存储索引字段数据以及主键索引位置;如果sql查询的字段超出非主键索引的存储字段,则需要根据非主键索引找到主键索引,再去查对应的额外数据,这个过程就叫做回表。具体参考mysql索引;
例如:user表有id,age,name,address字段,id为主键索引,根据(age,name)创建非主键索引;
select age,name,address from user where age = ? and name = ? ,会触发回表,先找到索引数据,再找到索引对应的主键索引位置,再找到主键索引拿到address数据;
代码层面
-
如果存在分页查询,分页查询时自定义查询总数语句,去除多余表关联;
例如:
传来岗位集合和考试id,查询每个岗位下的考试试题情况,可以直接查询考试下的题目数量*岗位集合size=总数,无需在关联 多余的表,只需要关心最细粒度的分页数据; -
如果需要查询没有依赖关系的多个数据,考虑多线程异步查询,耗时取多个任务中的最大值(countdownlatch);、
CountDownLatch countDownLatch = new CountDownLatch(4); // 开启4个线程异步执行查询 try{ // 第一次考試信息 erver.getFirstInfo(examInfoDOMap,orgId,uemIdsList,countDownLatch); // 最高分信息 erver.getTopInfo(topUserExamInfoDOHashMap,orgId,uemIdsList,countDownLatch); // 平均分&考试次数 erver.getOtherInfo(eaCntAndAvgScoreMap,orgId,uemIdsList,countDownLatch); // 正确答案 erver.getQuesCorrectAnswer(answerDetailMap,orgId,uemIdsList,countDownLatch); countDownLatch.await(); // 主线程等待4个线程执行结束才继续执行 }catch (Exception e){ e.printStackTrace(); } /** * 查询正确答案 * @param countDownLatch */ @Async public void getQuesCorrectAnswer(Map<String, List<OteUserAnswerDetail>> answerDetailMap, String orgId,List<String> uqIds,CountDownLatch countDownLatch,) { try { // -------- } catch (Exception e) { log.error(e.getMessage(), e); } finally { countDownLatch.countDown(); // finally一定要执行countdown(),否则会阻塞 } }
-
针对sql较多的任务,并行流很ok,针对计算任务较多的,线程数不宜过多,会造成额外的cpu切换开销;
-
并行流操作list,map之类的,切记一定要用CopyOnArrayList、ConcurrentHashMap之类的线程安全的集合,否则会空指针;
-
后续补充。。。