接口性能优化的技巧
一. 索引
索引优化的成本是最小的,可以通过线上日志或者监控报告,查到某个接口用到的某条sql语句的耗时。
1.1 没加索引
sql语句中where条件的关键字段,或者order by后面的排序字段,忘了加索引。
1.2 索引没生效
可以通过explain执行计划查看索引的使用情况
常见的索引失效原因:不满足最左前缀原则,范围索引列没有放最后,使用了select * ,索引列上有计算,索引列上使用了函数,字符类型没加引号,用is null和is not null没注意字段是否允许为空,like查询左边有%,使用or关键字时没有注意等等。
1.3 选错索引
明明是同一条sql,只是入参不同而已,有的时候走的索引a,有的时候走的是索引b?必要时可以使用force index来强制查询sql走某个索引。
二. sql优化
如果优化索引后,也没啥效果,就需要试着优化sql语句,相比较java代码来说,这种方式的成本要小得多。
sql优化技巧:
2.1 避免使用select * ,它不会走覆盖索引,会出现大量回表操作,从而导致查询sql的性能很低。在我们日常开发时,
应只查需要用到的列,多余的列无需查出来。
2.2 用union all 代替union ,我们都知道sql语句使用union关键字后,可以获取排重后的数据,而如果使用union all关键字,可以获取所有的数据,包含重复的数据。排重的过程需要遍历,排序和比较,它更耗时,更消耗cpu资源。除非业务场景需要,否则尽量使用union all。
2.3 小表驱动大表,in 适用于左边大表,右边小表,exists适用于左边小表,右边大表。
2.4 批量操作,在代码中重复性调用数据库,每次远程请求建立连接都是会消耗一定性能的,对于一些批量插入的业务场景,可以将多条sql合并为一条执行。
2.5 多用limit,
2.6 in中的值太多,尽量分批查询
2.7 增量查询,
2.8 高效的分页,将limit的开始位置使用where条件替换
2.9 用连接查询代替子查询,子查询会创建临时表,查询结束后再删除,有额外的性能消耗,可以使用连接查询优化
2.10 join的表不应该太多,尽量不超过3个
2.11 提升group by 的效率,先用where条件圈定范围,在使用group by
三. 远程调用
3.1 对于多个调用的场景可以使用线程池异步执行,结果的获取可以通过CompleteFuture去实现
3.2 数据异构,将不相关的数据全部排除,需要用的数据全部通过某个任务整合到一起,这样查询时只需要调用一次即可(适用于离线数据,如果对数据实时性要求较高,则有数据一致性的问题)
四. 异步处理
通过梳理业务逻辑,核心逻辑可以同步执行,同步写库,非核心逻辑,可以异步执行,异步写库。
通常异步主要有两种:多线程和mq。
五. 避免大事务
大事务可能引发的问题:死锁,回滚时间长,并发情况下数据库连接池被占满,锁等待,接口超时,数据库主从延迟等等...
优化方式:少用@Transactional注解,将查询方法放在事务外,事务中避免远程调用,事务中避免一次性处理太多数据,有些功能可以非事务执行,有些功能可以异步处理(调用类中的私有方法会使事务失效,可以通过代理调用)。
六. 锁粒度
在某些业务场景下, 为了防止多个线程并发修改某个临界资源,造成数据异常,我们会选择 加锁
6.1 synchronized
java 中提供了synchronized关键字给我们的代码加锁,通常是在方法上 和 在代码块上 加锁。但是在加锁的过程中,需要注意锁的范围,也就是锁粒度,尽量只加在会产生数据竞争的地方
6.2 redis分布式锁的八大坑
非原子操作:setNx 与 expire 命令分开执行,非原子操作
忘了释放锁:在try-cathc-finally 中增加释放锁的操作
释放了别人的锁:假设线程A和线程B都使用了lockKey加锁,线程A加锁成功了,但是由于业务逻辑执行耗时较长,锁超时释放了,这时线程B加上了锁,线程A执行结束后将lockKey释放,这就是一个典型的案例。所以在加锁的过程中,需要多设置一个requestId,自己只能释放自己加的锁,不允许释放别人加的锁。
大量失败请求:可以通过自旋锁解决
锁重入问题:使用可重入锁
锁竞争问题:细化锁粒度,使用读写锁,分段锁
锁超时问题:使用TimeTask实现自动续期功能(看门狗机制)自动续期的功能是获取锁之后开启一个定时任务,每隔十秒判断一下锁是否存在,如果存在则刷新过期时间,如果续期三次,也就是三十秒后。业务方法还是没有执行完,就不再续期了。
主从复制的问题:使用RedissonRedLock。
6.3 数据库分布式锁
mysql数据库主要有三种锁,
表锁:加锁快,不会出现死锁,但锁粒度大,发生锁冲突的概率最高,并发度最低。
行锁:加锁慢,会出现死锁,但锁粒度最小,发生锁冲突的概率最低,并发度也最高。
间隙锁:开销和加锁时间介于表锁和行锁之间,会出现死锁,锁粒度介于表锁和行锁之间,并发度一般。
在数据库锁的优化方向是:优先使用行锁,其次使用间隙锁,再使用表锁。
七. 分页处理
将一次获取所有的数据请求,改成分多次获取,每次只获取一部分用户的数据,最后进行合并和汇总。