后台优化内容整理,可作为开发规范
1.SQL优化方面
(1) exists和in
in是把外表和内表作hash连接,而exists是对外表作loop循环,每次loop循环再对内表进行查询,一直以来认为exists比in效率高的说法是不准确的。如果查询的两个表大小相当,那么用in和exists差别不大;如果两个表中一个较小一个较大,则子查询表大的用exists,
子查询表小的用in;
例如:表A(小表),表B(大表)
select * from A where cc in(select cc from B) -->效率低,用到了A表上cc列的索引;
select * from A where exists(select cc from B where cc=A.cc) -->效率高,用到了B表上cc列的索引。
相反的:
1 select * from B where cc in(select cc from A) -->效率高,用到了B表上cc列的索引
2 select * from B where exists(select cc from A where cc=B.cc) -->效率低,用到了A表上cc列的索引。
所以具体情况具体对待,我们项目中的多选查询条件,一般都是用的in,因为查询条件一般不会太多,如果是是用到的子查询,比如项目中出现的,先查符合条件的所有房型Id,再根据这些房型id查询所有的酒店id,此时用exists效率比较高,反过来就用in方式
(2)not exists和 not in
如果查询语句使用了not in,那么对内外表都进行全表扫描,没有用到索引;而not exists的子查询依然能用到表上的索引。所以无论哪个表大,用not exists都比not in 要快。
我们项目中用not in的不多,因为一般都不是子查询,都是固定的值,不存在条件出现null的情况,值也不多,所以经常都是直接写多个and判断了,如果查询条件过多的时候,需要使用not exists
(3)where条件中不用null判断
应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描,如:
select id from t where num is null
可以在num上设置默认值0,确保表中num列没有null值,然后这样查询:
select id from t where num=0
(4)应尽量避免在 where 子句中使用 or 来连接条件
应尽量避免在 where 子句中使用 or 来连接条件,否则将导致引擎放弃使用索引而进行全表扫描。而改用union之后,性能就大大提高了。
这样写的一段SQL:
select a.MemberID,a.MemberName,a.MemberPhone
from Member a,Member_Tmep b
where (a.MemberName = b.MemberName or a.MemberPhone = b.MemberPhone) and a.MemberID <> b.MemberID
改成了下面这样:
--查询出会员姓名相同但ID不同的记录
select a.MemberID,a.MemberName,a.MemberPhone
from Member a
inner join Member_Tmep b on a.MemberName = b.MemberName and a.MemberID <> b.MemberID
union
--再查询出会员电话相同但ID不同的记录,进行合并
select a.MemberID,a.MemberName,a.MemberPhone
from Member a
inner join Member_Tmep b on a.MemberPhone = b.MemberPhone and a.MemberID <> b.MemberID
这样再执行,秒秒钟就执行完了。
除了上述这种情况,还有一种常见的会使用or语句的情景,那就是:查询出某字段的值等于某几个特定值的记录。
例如,需要查询出会员姓名为“张三”、“李四”的记录。我们可能会这样写:
select * from Member where MemberName = '张三' or MemberName = '李四'
通常情况下,这种写法是看不出有什么问题的,但是在数据量很大的情况下,一样会非常影响执行速度。
还有一种写法是使用in语句,例如下面这样:
select * from Member where MemberName in ('张三','李四')
但是有些说法认为in语句一样会导致全表扫描。in和not in的写法都是应该尽量避免的。
如果需要查询的特定值是连续的数值范围,如90–100,可以改用bwteen…and语句。例如:
select * from Member where MemberID between 90 and 100
如果无法使用bwteen…and,那么仍然需要使用union方法了,如:
select * from Member where MemberName = '张三'
union all
select * from Member where MemberName = '李四'
这里因为会员姓名为“张三”的和为“李四”的不可能有重复记录,因此可以使用性能更高的union all,而不是union了。
(5)应尽量避免在where子句中对字段进行函数操作,这将导致引擎放弃使用索引而进行全表扫描。如:
select id from t where substring(name,1,3)='abc'--name以abc开头的id
select id from t where datediff(day,createdate,'2005-11-30')=0--‘2005-11-30’生成的id
应改为:
select id from t where name like 'abc%'
select id from t where createdate>='2005-11-30' and createdate<'2005-12-1'
(6)正确使用索引
唯一索引,对于查询用到的字段,尽可能使用唯一索引。
在经常进行连接,但是没有指定为外键的列上建立索引。
在频繁进行排序会分组的列上建立索引,如经常做group by 或 order by 操作的字段。
索引并不是越多越好,索引固然可以提高相应的 select 的效率,但同时也降低了 insert 及 update 的效率,因为 insert 或 update 时有可能会重建索引,所以怎样建索引需要慎重考虑,视具体情况而定。一个表的索引数最好不要超过6个,若太多则应考虑一些不常使用到的列上建的索引是否有必要。
2.service优化方面(代码)
(1)尽量重用对象
特别是String对象的使用,出现字符串连接时应该使用StringBuilder/StringBuffer代替。由于Java虚拟机不仅要花时间生成对象,以后可能还需要花时间对这些对象进行垃圾回收和处理,因此,生成过多的对象将会给程序的性能带来很大的影响。
(2)尽可能使用局部变量
调用方法时传递的参数以及在调用中创建的临时变量都保存在栈中速度较快,其他变量,如静态变量、实例变量等,都在堆中创建,速度较慢。另外,栈中创建的变量,随着方法的运行结束,这些内容就没了,不需要额外的垃圾回收。
(3)尽量减少对变量的重复计算
如下面的操作:
for (int i = 0; i < list.size(); i++)
{…}
建议替换为:
for (int i = 0, int length = list.size(); i < length; i++)
{…}
(4)尽量采用懒加载的策略,即在需要的时候才创建
如下面的操作:
String str = “aaa”;
if (i == 1){
list.add(str);
}
建议替换为:
if (i == 1){
String str = “aaa”;
list.add(str);
}
(5)循环内不要不断创建对象引用
for (int i = 1; i <= count; i++) {
Object obj = new Object();
}
这种做法会导致内存中有count份Object对象引用存在,count很大的话,就耗费内存了,建议为改为
Object obj = null;
for (int i = 1; i <= count; i++) {
obj = new Object();
}
这样的话,内存中只有一份Object对象引用,每次new Object()的时候,Object对象引用指向不同的Object罢了,但是内存中只有一份,这样就大大节省了内存空间了。
(6)实现RandomAccess接口的集合比如ArrayList,应当使用最普通的for循环而不是foreach循环来遍历
这是JDK推荐给用户的。JDK API对于RandomAccess接口的解释是:实现RandomAccess接口用来表明其支持快速随机访问,此接口的主要目的是允许一般的算法更改其行为,从而将其应用到随机或连续访问列表时能提供良好的性能。实际经验表明,实现RandomAccess接口的类实例,假如是随机访问的,使用普通for循环效率将高于使用foreach循环;反过来,如果是顺序访问的,则使用Iterator会效率更高。可以使用类似如下的代码作判断:
if (list instanceof RandomAccess)
{ for (int i = 0; i < list.size(); i++){}
}else{
Iterator<?> iterator = list.iterable(); while (iterator.hasNext()){iterator.next()}
}
foreach循环的底层实现原理就是迭代器Iterator,参见Java语法糖1:可变长度参数以及foreach循环原理。所以后半句”反过来,如果是顺序访问的,则使用Iterator会效率更高”的意思就是顺序访问的那些类实例,使用foreach循环去遍历。
所以,以后如果是数组或ArrayList就用下标循环遍历,如果是LinkedList就用foreach循环遍历。当然 这只是在考虑性能时候的做法,如果不考虑性能还用foreach比较好,代码简洁。
(7)不要创建一些不使用的对象,不要导入一些不使用的类
即不要在项目中出现感叹号!
3.项目中页面出现加载过慢现象的原因
(1)数据库数据量过大,且该表是作为关联表进行查询的,导致查询过慢
例如:导游列表那边查询导游数据的时候需要关联一张价格表,这张价格表的数据180w条,查询所有导游一天的数据就有1w条,耗时4.7秒,整个sql耗时5.5秒,外加service中还有业务处理,导致后台加载严重超时
解决方法:当时是添加索引,我这边解决的方法是在where条件的那个字段添加索引,查询速率瞬间变快
(2)service中出现双循环并且在循环中访问数据库
看一下这边的代码,这里面出现了很多对性能不友好的东西,而且应该都有人这样写过
- 图中第一点,循环同样一个list,为什么出现了两次,为什么不合并
- 图中第二点,循环里面多次创建对象,导致出现多个引用,消耗堆内存
- 图中第三点,循环中多次交互数据库,是否可以考虑一次性读取数据
(3)跳转页面方法中出现大量查询导致跳转等待时间过长
看一下这边的代码,多次查询,甚至分页查询也写在跳转方法中
如果数据量比较大时,查询耗时比较长,页面跳转时间会很长,用户体验度会很差
所以,尽量根据不同板块使用ajax进行异步加载数据,跳转方法中尽量不要出现复杂的业务逻辑查询,避免出现查询时间较长的方法
以及图片使用懒加载