Java后端服务接口性能优化常用技巧

前言

对于高标准程序员来说提供高性能的服务接口是我们所追求的目标,以下梳理了一些提升接口性能的技术方案,希望对大家有所帮助。

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 索引不生效
有时候虽然添加了索引,但是索引可能会失效,例如下面情况:

  1. 参数类型与字段类型不匹配,导致类型发生了隐式转换,索引失效
  2. 查询条件包含or
  3. like模糊查询时,模糊匹配的占位符位于条件的首部
  4. 在联合索引的场景下,查询条件不满足最左匹配原则
  5. 使用了select *(在联合索引下,尽量使用明确的查询列来趋向于走覆盖索引)
  6. 索引列参与了函数处理,会导致全表扫描,索引失效
  7. 索引列参与了运算,会导致全表扫描,索引失效
  8. 查询条件使用 not in 时,如果是主键则走索引,如果是普通索引,则索引失效
  9. 查询条件使用 is null 时正常走索引,使用 is not null 时,不走索引
  10. 查询条件使用 not exists 时,索引失效
  11. order by导致索引失效(看情况)
  12. group by索引失效
  13. 参数不同导致索引失效

1.3 索引设计是否合理

索引不是设计越多越好,设计必须要合理,例如:优先考虑设计联合索引,适当使用覆盖索引;索引个数尽量不要超过5个;索引最好选择数据区分度较高的字段,如性别太多重复字段就不适合创建索引。

2.慢SQL优化

在索引优化之后,还可以进一步优化慢SQL语句,如下梳理10条慢sql优化建议:

  1. 尽量不用select *,查询具体需要的字段
  2. 尽量减少join关联表,部分用Java代码处理
  3. 小表驱动大表,结果集最小化
  4. in和not in中元素别太多
  5. 优化group by、order by 保证走索引
  6. 字段类型要合理使用,不要都是varchar
  7. 尽量用union all 替换union
  8. 优化limit深度分页问题
  9. 多用主键和覆盖索引,尽量减少回表
  10. 多用limit或者分页,限制返回条数

3.异步执行

对于一些耗时操作或者不影响主要业务的逻辑,可以采用异步执行,来提升性能。
在这里插入图片描述
为了降低接口耗时,及时返回结果,可以把短信发送、日志写入及积分赠送通过异步执行。

类似的场景还有:用户下订单之后的消息发送及赠送积分也可以放到异步处理。

常见的异步实现:线程池、消息队列MQ、Spring注解@Async、异步框架CompletableFuture、Spring ApplicationEvent事件。

4.批量处理

数据库操作或者远程调用时,能批量操作就不要for循环调用:

  1. 我们平时一个列表明细数据插入数据库时,不要在for循环一条一条插入,建议一个批次几百条,进行批量插入,减少多次IO,建议使用Mybatis 的foreach操作,不过数量也不要一次太多(100),MP的saveBatch、或者PreparedStatement的addBatch();

  2. 同理远程调用类似,比如你查询营销标签是否命中,可以一个标签一个标签去查,也可以批量标签去查,那批量进行,效率就更高。

//反例
for(int i = 0; i < n; i++){
  singleUpdate(param)
}
//正例
batchUpdate(param);

5.数据预加载

数据预加载策略,顾名思义就是提前把部分要用到的数据,初始化到缓存(Redis)。如果你在未来某个时间需要用到某个经过复杂计算的数据,才实时去计算的话,可能耗时比较大。这时候我们可以采取预取思想,提前把可能需要的数据计算好,放到缓存中,等需要的时候,去缓存取就行。这将大幅度提高接口性能。

6.池化技术(多线程)

池化技术最常见的是线程池应用:

  1. 如果你每次需要用到线程,都去创建,就会有增加一定的耗时;

  2. 线程池可以重复利用线程,避免不必要的耗时;

  3. 池化技术不仅仅指线程池,很多场景都有池化思想的体现,它的本质就是预分配与循环使用。

8.事件回调机制

  1. 如果你调用一个系统B的接口,但是它处理业务逻辑,耗时需要10s甚至更多。然后你是一直阻塞等待,直到系统B的下游接口返回,再继续你的下一步操作吗?这样显然不合理。

  2. 我们可以采用事件回调机制,即我们不用阻塞等待系统B的接口,而是先去做别的操作。等系统B的接口处理完,通过事件回调通知,我们接口收到通知再进行对应的业务操作即可。如IO多路复用模型实现。

9.串行改为并行调用

假设我们设计一个为每个家长发送短信的接口,它需要查寻每个家长,然后再发送短信。那你是一个一个家长发送短信,还是并行调用呢?

  1. 可以使用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主键索引。但是这种方式有局限性:需要一种类似连续自增的字段,而且需要前端把上次最大值传给后端。

  • 21
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值