JAVA代码优化,接口优化,SQL优化 (小技巧)(六)

简介: JAVA代码优化,接口优化,SQL优化 (小技巧)(六)

🟡第三章:SQL 优化



1 避免使用select *

很多时候,我们写sql语句时,为了方便,喜欢直接使用select *,一次性查出表中所有列的数据。


反例:

select * from user where id=1;

在实际业务场景中,可能我们真正需要使用的只有其中一两列。查了很多数据,但是不用,白白浪费了数据库资源,比如:内存或者cpu。


此外,多查出来的数据,通过网络IO传输的过程中,也会增加数据传输的时间。


还有一个最重要的问题是:select *不会走覆盖索引,会出现大量的回表操作,而从导致查询sql的性能很低。


那么,如何优化呢?


正例:

select name,age from user where id=1;

sql语句查询时,只查需要用到的列,多余的列根本无需查出来。


2 用union all代替union

我们都知道sql语句使用union关键字后,可以获取排重后的数据。


而如果使用union all关键字,可以获取所有数据,包含重复的数据。


反例:

(select * from user where id=1) 
union 
(select * from user where id=2);


排重的过程需要遍历、排序和比较,它更耗时,更消耗cpu资源。


所以如果能用union all的时候,尽量不用union。


正例:

(select * from user where id=1) 
union all
(select * from user where id=2);


除非是有些特殊的场景,比如union all之后,结果集中出现了重复数据,而业务场景中是不允许产生重复数据的,这时可以使用union。


3 小表驱动大表

小表驱动大表,也就是说用小表的数据集驱动大表的数据集。


假如有order和user两张表,其中order表有10000条数据,而user表有100条数据。


这时如果想查一下,所有有效的用户下过的订单列表。


可以使用in关键字实现:

select * from order
where user_id in (select id from user where status=1)


也可以使用exists关键字实现:

select * from order
where exists (select 1 from user where order.user_id = user.id and status=1)


前面提到的这种业务场景,使用in关键字去实现业务需求,更加合适。


为什么呢?


因为如果sql语句中包含了in关键字,则它会优先执行in里面的子查询语句,然后再执行in外面的语句。如果in里面的数据量很少,作为条件查询速度更快。


而如果sql语句中包含了exists关键字,它优先执行exists左边的语句(即主查询语句)。然后把它作为条件,去跟右边的语句匹配。如果匹配上,则可以查询出数据。如果匹配不上,数据就被过滤掉了。


这个需求中,order表有10000条数据,而user表有100条数据。order表是大表,user表是小表。如果order表在左边,则用in关键字性能更好。


总结一下:


in 适用于左边大表,右边小表。

exists 适用于左边小表,右边大表。

不管是用in,还是exists关键字,其核心思想都是用小表驱动大表。


4 批量操作

如果你有一批数据经过业务处理之后,需要插入数据,该怎么办?


反例:

for(Order order: list){
   orderMapper.insert(order):
}


在循环中逐条插入数据。

insert into order(id,code,user_id) 
values(123,'001',100);


该操作需要多次请求数据库,才能完成这批数据的插入。


但众所周知,我们在代码中,每次远程请求数据库,是会消耗一定性能的。而如果我们的代码需要请求多次数据库,才能完成本次业务功能,势必会消耗更多的性能。


那么如何优化呢?


正例:

orderMapper.insertBatch(list):

提供一个批量插入数据的方法。

insert into order(id,code,user_id) 
values(123,'001',100),(124,'002',100),(125,'003',101);


这样只需要远程请求一次数据库,sql性能会得到提升,数据量越多,提升越大。


但需要注意的是,不建议一次批量操作太多的数据,如果数据太多数据库响应也会很慢。批量操作需要把握一个度,建议每批数据尽量控制在500以内。如果数据多于500,则分多批次处理。


5 多用limit

有时候,我们需要查询某些数据中的第一条,比如:查询某个用户下的第一个订单,想看看他第一次的首单时间。


反例:


select id, create_date 
 from order 
where user_id=123 
order by create_date asc;


根据用户id查询订单,按下单时间排序,先查出该用户所有的订单数据,得到一个订单集合。 然后在代码中,获取第一个元素的数据,即首单的数据,就能获取首单时间。

List list = orderMapper.getOrderList();
Order order = list.get(0);


虽说这种做法在功能上没有问题,但它的效率非常不高,需要先查询出所有的数据,有点浪费资源。


那么,如何优化呢?


正例:

select id, create_date 
 from order 
where user_id=123 
order by create_date asc 
limit 1;


使用limit 1,只返回该用户下单时间最小的那一条数据即可。


此外,在删除或者修改数据时,为了防止误操作,导致删除或修改了不相干的数据,也可以在sql语句最后加上limit。


例如:

update order set status=0,edit_time=now(3) 
where id>=100 and id<200 limit 100;


这样即使误操作,比如把id搞错了,也不会对太多的数据造成影响。


6 in中值太多

对于批量查询接口,我们通常会使用in关键字过滤出数据。比如:想通过指定的一些id,批量查询出用户信息。


sql语句如下:

select id,name from category
where id in (1,2,3...100000000);


如果我们不做任何限制,该查询语句一次性可能会查询出非常多的数据,很容易导致接口超时。


这时该怎么办呢?

select id,name from category
where id in (1,2,3...100)
limit 500;


可以在sql中对数据用limit做限制。


不过我们更多的是要在业务代码中加限制,伪代码如下:

public List getCategory(List ids) {
   if(CollectionUtils.isEmpty(ids)) {
      return null;
   }
   if(ids.size() > 500) {
      throw new BusinessException("一次最多允许查询500条记录")
   }
   return mapper.getCategoryList(ids);
}


还有一个方案就是:如果ids超过500条记录,可以分批用多线程去查询数据。每批只查500条记录,最后把查询到的数据汇总到一起返回。


不过这只是一个临时方案,不适合于ids实在太多的场景。因为ids太多,即使能快速查出数据,但如果返回的数据量太大了,网络传输也是非常消耗性能的,接口性能始终好不到哪里去。

相关文章
|
7月前
|
安全 Java 编译器
new出来的对象,不一定在堆上?聊聊Java虚拟机的优化技术:逃逸分析
逃逸分析是一种静态程序分析技术,用于判断对象的可见性与生命周期。它帮助即时编译器优化内存使用、降低同步开销。根据对象是否逃逸出方法或线程,分析结果分为未逃逸、方法逃逸和线程逃逸三种。基于分析结果,编译器可进行同步锁消除、标量替换和栈上分配等优化,从而提升程序性能。尽管逃逸分析计算复杂度较高,但其在热点代码中的应用为Java虚拟机带来了显著的优化效果。
243 4
|
7月前
|
数据采集 JSON Java
Java爬虫获取1688店铺所有商品接口数据实战指南
本文介绍如何使用Java爬虫技术高效获取1688店铺商品信息,涵盖环境搭建、API调用、签名生成及数据抓取全流程,并附完整代码示例,助力市场分析与选品决策。
|
5月前
|
消息中间件 缓存 Java
Spring框架优化:提高Java应用的性能与适应性
以上方法均旨在综合考虑Java Spring 应该程序设计原则, 数据库交互, 编码实践和系统架构布局等多角度因素, 旨在达到高效稳定运转目标同时也易于未来扩展.
382 8
|
6月前
|
算法 安全 Java
除了类,Java中的接口和方法也可以使用泛型吗?
除了类,Java中的接口和方法也可以使用泛型吗?
220 11
|
5月前
|
SQL 存储 监控
SQL日志优化策略:提升数据库日志记录效率
通过以上方法结合起来运行调整方案, 可以显著地提升SQL环境下面向各种搜索引擎服务平台所需要满足标准条件下之数据库登记作业流程综合表现; 同时还能确保系统稳健运行并满越用户体验预期目标.
336 6
|
5月前
|
Java Go 开发工具
【Java】(9)抽象类、接口、内部的运用与作用分析,枚举类型的使用
抽象类必须使用abstract修饰符来修饰,抽象方法也必须使用abstract修饰符来修饰,抽象方法不能有方法体。抽象类不能被实例化,无法使用new关键字来调用抽象类的构造器创建抽象类的实例。抽象类可以包含成员变量、方法(普通方法和抽象方法都可以)、构造器、初始化块、内部类(接 口、枚举)5种成分。抽象类的构造器不能用于创建实例,主要是用于被其子类调用。抽象类中不一定包含抽象方法,但是有抽象方法的类必定是抽象类abstract static不能同时修饰一个方法。
282 1
|
6月前
|
Java Spring
如何优化Java异步任务的性能?
本文介绍了Java中四种异步任务实现方式:基础Thread、线程池、CompletableFuture及虚拟线程。涵盖多场景代码示例,展示从简单异步到复杂流程编排的演进,适用于不同版本与业务需求,助你掌握高效并发编程实践。(239字)
351 6
|
7月前
|
存储 缓存 安全
Java集合框架(二):Set接口与哈希表原理
本文深入解析Java中Set集合的工作原理及其实现机制,涵盖HashSet、LinkedHashSet和TreeSet三大实现类。从Set接口的特性出发,对比List理解去重机制,并详解哈希表原理、hashCode与equals方法的作用。进一步剖析HashSet的底层HashMap实现、LinkedHashSet的双向链表维护顺序特性,以及TreeSet基于红黑树的排序功能。文章还包含性能对比、自定义对象去重、集合运算实战和线程安全方案,帮助读者全面掌握Set的应用与选择策略。
727 23
|
6月前
|
数据采集 存储 弹性计算
高并发Java爬虫的瓶颈分析与动态线程优化方案
高并发Java爬虫的瓶颈分析与动态线程优化方案
|
7月前
|
安全 Java 开发者
Java集合框架:详解Deque接口的栈操作方法全集
理解和掌握这些方法对于实现像浏览器后退功能这样的栈操作来说至关重要,它们能够帮助开发者编写既高效又稳定的应用程序。此外,在多线程环境中想保证线程安全,可以考虑使用ConcurrentLinkedDeque,它是Deque的线程安全版本,尽管它并未直接实现栈操作的方法,但是Deque的接口方法可以相对应地使用。
412 12