接口性能优化常用技巧

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

参数类型与字段类型不匹配,导致类型发生了隐式转换,索引失效查询条件包含orlike模糊查询时,模糊匹配的占位符位于条件的首部在联合索引的场景下,查询条件不满足最左匹配原则使用了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主键索引。但是这种方式有局限性:需要一种类似连续自增的字段,而且需要前端把上次最大值传给后端。

好文阅读

评论可见,请评论后查看内容,谢谢!!!评论后请刷新页面。