接口性能优化常用技巧
- 前言
- 1.数据库索引
- 2.慢SQL优化
- 3.异步执行
- 4.批量处理
- 5.数据预加载
- 6.池化技术(多线程)
- 8.事件回调机制
- 9.串行改为并行调用
- 10.深度分页问题
前言
对于高标准程序员来说提供高性能的服务接口是我们所追求的目标,以下梳理了一些提升接口性能的技术方案,希望对大家有所帮助。
1.数据库索引
当接口响应慢时,我们可能会去排查是否是数据库查询慢了,进而会去关注数据库查询优化,而索引优化是代价最小的且效果很明显的优化方式。索引优化主要从以下几个角度考虑:
- SQL是否添加索引?
- 索引是否生效?
- 索引设计是否合理?
1.1 SQL是否添加索引
在开发阶段就要考虑数据库表的索引设计,对于一些经常作为检索条件、order by、group by 后面的字段,且数据区分度高的字段可以考虑创建索引。
#可以通过explain执行查询计划,查看SQL执行情况
explain select * from t_user where name like '%黄';
#也可以通过命令show create table user 检查整张表的索引情况
show create table t_user
#如果某个表忘记添加某个索引,可以通过命令添加索引
alter table t_user add index idx_name (name);
注意: 在数据量很大的表中创建索引,最好选择在业务不繁忙时间段,避免影响线上业务。
1.2 索引不生效
有时候虽然添加了索引,但是索引可能会失效,例如下面情况:
- 参数类型与字段类型不匹配,导致类型发生了隐式转换,索引失效
- 查询条件包含or
- like模糊查询时,模糊匹配的占位符位于条件的首部
- 在联合索引的场景下,查询条件不满足最左匹配原则
- 使用了select *(在联合索引下,尽量使用明确的查询列来趋向于走覆盖索引)
- 索引列参与了函数处理,会导致全表扫描,索引失效
- 索引列参与了运算,会导致全表扫描,索引失效
- 查询条件使用 not in 时,如果是主键则走索引,如果是普通索引,则索引失效
- 查询条件使用 is null 时正常走索引,使用 is not null 时,不走索引
- 查询条件使用 not exists 时,索引失效
- order by导致索引失效(看情况)
- group by索引失效
- 参数不同导致索引失效
1.3 索引设计是否合理
索引不是设计越多越好,设计必须要合理,例如:优先考虑设计联合索引,适当使用覆盖索引;索引个数尽量不要超过5个;索引最好选择数据区分度较高的字段,如性别太多重复字段就不适合创建索引。
2.慢SQL优化
在索引优化之后,还可以进一步优化慢SQL语句,如下梳理10条慢sql优化建议:
- 尽量不用select *,查询具体需要的字段
- 尽量减少join关联表,部分用Java代码处理
- 小表驱动大表,结果集最小化
- in和not in中元素别太多
- 优化group by、order by 保证走索引
- 字段类型要合理使用,不要都是varchar
- 尽量用union all 替换union
- 优化limit深度分页问题
- 多用主键和覆盖索引,尽量减少回表
- 多用limit或者分页,限制返回条数
3.异步执行
对于一些耗时操作或者不影响主要业务的逻辑,可以采用异步执行,来提升性能。
为了降低接口耗时,及时返回结果,可以把短信发送、日志写入及积分赠送通过异步执行。
类似的场景还有:用户下订单之后的消息发送及赠送积分也可以放到异步处理。
常见的异步实现:线程池、消息队列MQ、Spring注解@Async、异步框架CompletableFuture、Spring ApplicationEvent事件。
4.批量处理
数据库操作或者远程调用时,能批量操作就不要for循环调用:
-
我们平时一个列表明细数据插入数据库时,不要在for循环一条一条插入,建议一个批次几百条,进行批量插入,减少多次IO,建议使用Mybatis 的foreach操作,不过数量也不要一次太多(100),MP的saveBatch、或者PreparedStatement的addBatch();
-
同理远程调用类似,比如你查询营销标签是否命中,可以一个标签一个标签去查,也可以批量标签去查,那批量进行,效率就更高。
//反例
for(int i = 0; i < n; i++){
singleUpdate(param)
}
//正例
batchUpdate(param);
5.数据预加载
数据预加载策略,顾名思义就是提前把部分要用到的数据,初始化到缓存(Redis)。如果你在未来某个时间需要用到某个经过复杂计算的数据,才实时去计算的话,可能耗时比较大。这时候我们可以采取预取思想,提前把可能需要的数据计算好,放到缓存中,等需要的时候,去缓存取就行。这将大幅度提高接口性能。
6.池化技术(多线程)
池化技术最常见的是线程池应用:
-
如果你每次需要用到线程,都去创建,就会有增加一定的耗时;
-
线程池可以重复利用线程,避免不必要的耗时;
-
池化技术不仅仅指线程池,很多场景都有池化思想的体现,它的本质就是预分配与循环使用。
8.事件回调机制
-
如果你调用一个系统B的接口,但是它处理业务逻辑,耗时需要10s甚至更多。然后你是一直阻塞等待,直到系统B的下游接口返回,再继续你的下一步操作吗?这样显然不合理。
-
我们可以采用事件回调机制,即我们不用阻塞等待系统B的接口,而是先去做别的操作。等系统B的接口处理完,通过事件回调通知,我们接口收到通知再进行对应的业务操作即可。如IO多路复用模型实现。
9.串行改为并行调用
假设我们设计一个为每个家长发送短信的接口,它需要查寻每个家长,然后再发送短信。那你是一个一个家长发送短信,还是并行调用呢?
- 可以使用CompletableFuture 并行调用提高性能,类似也可以使用多线程处理。
10.深度分页问题
数据库的深度分页问题,比较影响接口性能,如下所示SQL语句:
select
id,name,balance from account
where
create_time > '2023-09-19'
limit 100000, 10;
limit 100000,10意味着会扫描100010行,丢弃掉前100000行,最后返回10条数据。
select id,name,balance FROM account where id > 100000 limit 10;
这样的话,后面无论翻多少页,性能都会不错的,因为命中了id主键索引。但是这种方式有局限性:需要一种类似连续自增的字段,而且需要前端把上次最大值传给后端。