性能调优
1、设计调优
宏观层面质的优化
2、代码调优
熟悉相关API,并在合适的场景中正确使用相关API或类库,同时,对算法、数据结构的灵活运用也是代码优化的重要内容
3、JVM调优
代码和JVM属于系统微观层面量的优化
4、数据库调优
使用preparestatement代替statement提高查询效率
Select,使用要查询的具体的列名,避免使用*号,
合理地使用冗余字段
Oracle的分区表
根据不同的数据,以Oracle为例,设置合理大小的共享池、缓存缓冲区或者PGA
5、操作系统调优
成为性能瓶颈的因素很多,比如:磁盘I/O,网络I/O,CPU,内存等。
找到性能瓶颈后,首先需要定位相关代码,确认是否在软件实现上存在问题或者优化空间。若有,则进行代码优化;若已经没有代码优化空间,则需要考虑进行JVM层面、数据库或者操作系统的优化,再者甚至要考虑修改原有的设计,或者提高硬件性能。
优化过程往往伴随着一些风险和弊端,为了优化某一段代码的实现,修改原有的算法,很可能引入新的bug。性能调优必有明确的目标,不要为了调优而调优,如果当前程序并没有明显的性能问题,盲目地进行调整,风险可能远远大于收益。
一、设计优化
1、善用设计模式
单例模式(例如Spring默认创建的对象就是单例)
a、对于频繁使用的对象,可以省略创建对象所花费的时间,对于那些重量级的对象而言是非常可观的一笔系统开销,如hibernate的sessionFactory
b、由于new操作的次数减少,因而对系统内存的使用频率也会降低,这将减轻GC压力,缩短GC停顿时间 。
由于使用懒汉模式需要用到同步来反正创建多个对象,反而降低了系统的性能,所以可以使用单例模式内部类来维护,如:
class A{ private A(){} private static class B{ private static A instance = new A(); } public static A getInstance(){ return B.instance; } } |
代理模式
动态代理类的字节码将在运行时生产成并载入当前的ClassLoader.
注:动态代理类的方法很多,如JDK自带的动态代理、CGLIB、Javassist或者ASM。JDK的动态代理使用简单,但功能相对较弱。CGLIB和Javassist都是高级的字节码生成库,总体性能比JDK自带的动态代理好,功能强大。ASM是低级的字节码生成工具,对开发人员要求最高,也是性能最好的一种动态代理的生成工具,但是使用过于繁琐,性能也没没有数量级的提升,可维护性差,故不推荐使用。
其中JDK的动态代理类创建最快,因为在其内置实现中defineClass()被定义为native,这方面性能高于其他几种实现。但是在代理类的函数调用性能上,JDK的动态代理不如CGLIB和Javassist,在实际开发中应用中,代理类的方法调用频率要远远高于代理类的实际生产频率,故动态代理对象方法调用的性能应该作为性能的主要关注点。
2、常用优化组件和方法
缓冲
缓冲区是一块特定的内存区域。开辟缓冲区的目的是通过缓解应用程序上下层之间的性能差异,提高系统的性能。
缓冲最长用的场景就是提高I/O的速度,为此,JDK内不少I/O组件都提供了缓冲功能,比如,当使用FileWriter是,为了进行I/O优化,BufferedWriter就是为FileWriter对象增加缓冲的功能。
缓冲区不宜过小,过小的缓冲区无法起到真正的缓冲作用,过大的缓冲区则会浪费系统内存,增加GC负担。
缓存
缓存也是一块为提升系统性能而开辟的内存空间,缓存的主要作用是暂存数据处理结果,并提供下次访问使用。目前的浏览器都会在本地缓存远程的页面从而减少远程http访问次数,在Java中,最简单的缓存可是直接使用HashMap实现。但是这样做会遇到很多问题,比如何时应该清理无效的数据,如果防止缓存数据过多导致的内存溢出等。
目前有很多基于Java的缓存框架,比如Hibernate使用的就是EHCache.
对象池
对象池是指,如果一个类被频繁请求使用,那么不必每次都生成一个实例,可以将这个类的一些实例保存在一个“池”中,待需要使用的时候直接从池中获取。如经常使用的数据库连接池。
注意:只用对重量级对象使用对象池技术才能提高系统性能,对轻量级的对象使用对象池,可能反而会降低系统性能。
负载均衡
集群terracotta
读写分离
时间转换空间
由于系统资源有限,为了在有限的资源内,达成某些特定的性能目标,就需要使用时间换空间的方法。
空间换时间
空间换时间是尝试使用更多的内存或者磁盘空间换取CPU资源或者网络资源等,通过增加系统内存消耗,来加快程序的运行速度。
这种方法的典型应用就是缓存。
二、Java程序优化
String(final类)
a、subString()方法的内存泄露
通过查看源码,发现使用这个方法时,会返回一个新的String对象,截取的字符串包含了原生字符串的所有内容,并占据了相应的内存空间,而仅仅通过偏移量和长度来决定自己的实际取值,这种算法提高了运算速度,却浪费了大量的内存空间,采取了空间换时间的策略。
b、字符串切割
使用StringTokenizer来代替split方法。
c、StringBuffer和StringBuilder
在需要对字符串进行修改操作时,使用StringBuffer和StringBuilder要优于String.
List接口
ArrayList和LinkedList的选择
初始化容量对List有影响
Map接口
初始化Map容量对Map性能有影响,HashMap的初始化大小为16,负载因子为0.75
HashMap的操作几乎等价于对数组的随机访问操作,具有很好的性能,但是如果hashCode或hash方法实现较差,在大量冲突产生的情况下,HashMap事实上就退化为几个链表,对HashMap的操作等价于遍历链表,性能很差。
Hash算法必须是高效的
Hash值到内存的地址的算法是快速的
根据内存地址可以直接取得对应的值
Hash冲突
HashMap内部维护一个Entry数组,每个Entry表项包括key、value、next和hash几项。这里的next指向另外一个Entry,当使用put()方法操作有冲突时,新的Entry依然会被放进去,并替换原有得值,同时为了保证旧值不丢失,会将新的Entry的next指向旧值,实现了一个数组索引空间内存放多个值项。HashMap实际上是一个链表的数组。
LinkedHashMap
继承于HashMap,在内部又增加了一个链表,用于存放元素的顺序。
Set
Set的实现是对应的Map的一种实现
优化集合访问代码
分离循环中被重复调用的代码
List list = new LinkedList(); for (int i = 0; i < list.size(); i++) {} |
可以吧list.size()提取出来,用int size=list.size(),然后用i<size比较
处理大并发分布式I/O时,可以使用nio替代传统的IO
有助于改善性能的技巧
慎用异常
Try-catch语句对系统性能是非常糟糕的。
多使用局部变量
展开循环
int[] arr = new int[39999999]; for (int i = 0; i < arr.length; i+=3) { arr[i]=i; // arr[i+1]=i+1; // arr[i+2]=i+2; } |
在循环体中实例化临时变量将会增加内存消耗
例子:
import java.util.vector; public class loop { void method (vector v) { for (int i=0;i < v.size();i++) { object o = new object(); o = v.elementat(i); } } } |
更正:
在循环体外定义变量,并反复使用
import java.util.vector; public class loop { void method (vector v) { object o; for (int i=0;i<v.size();i++) { o = v.elementat(i); } } } |
三、JVM
JVM运行时数据区
Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同数据区域。这些区域都有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进程的启动而存在,有些区域则是依赖用户线程的启动和结束而建立和销毁。
jvm运行时数据区域解析
一、程序计数器(寄存器)
当前线程所执行的字节码行号指示器
字节码解释器工作依赖计数器控制完成
通过执行线程行号记录,让线程轮流切换各条线程之间计数器互不影响
线程私有,生命周期与线程相同,随JVM启动而生,JVM关闭而死
线程执行Java方法时,记录其正在执行的虚拟机字节码指令地址
线程执行Nativan方法时,计数器记录为空(Undefined)
唯一在Java虚拟机规范中没有规定任何OutOfMemoryError情况区域
程序计数器(Program Counter Register)是一块较小的内存空间,它的作用可以看做是当前线程所执行的字节码行号指示器。字节码解释器工作时就是通过改变这个计数器的值来选取下 一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。
由于Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器(对于多喝处理器来说是一个内核) 只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间的计数器互不影响、独立存 储,我们称这类内存区域为“线程私有”的内存。
如果线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是Natvie方法,这个计数器值则为空 (Undefined)。此内存区域是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。
二、Java虚拟机栈
线程私有,生命周期与线程相同
用于存储局部变量、操作栈、动态链接、方法出口
每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程
与程序计数器一样,Java虚拟机栈(Java Vitual Machine Stacks)也是线程私有的,他的生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧 (Stack Frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的 过程。
局部变量表存放了编译器克制的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(Object reference)和字节码指令地址(returnAddress类型)。
在Java虚拟机规范中,对于此区域规定了两种异常状况:
如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常;
如果虚拟机栈可以动态扩展,当扩展时无法申请到足够的内存时会抛出OutOfMemoryError异常。
对于32位的jvm,默认大小为256kb, 而64位的jvm, 默认大小为512kb,可以通过-Xss设置虚拟机栈的最大值。不过如果设置过大,会影响到可创建的线程数量。
三、本地方法栈
本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用非常类似,区别在于虚拟机栈为虚拟机执行Java方法服务,而本地方法栈则是为虚拟机使用到的Native方法服务。
四、Java堆
Java堆(java heap)是Java虚拟机所管理的内存中最大的一块
它是被所有线程共享的一块内存区域,在虚拟机启动时创建
Java堆是垃圾收集管理的主要战场。根据Java虚拟机规范的规定,Java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可,就像我们的 磁盘空间一样。在实现时,既可以实现成固定大小的,也可以是可扩展的,不过当前主流的虚拟机都是按照可扩展来实现的。(通过-Xmx和-Xms控制)
如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常。
四、方法区
方法区(Method Area)与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
Java虚拟机规范对这个区域的限制非常宽松,除了和Java堆一样不需要连续的内存和可以选择固定大小或者可扩展外,还可以选择不实现垃圾收集。
根据Java虚拟机规范的规定,当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常。
内存溢出
堆内存溢出
java.lang.OutOfMemoryError:Java head space
栈内存溢出
Exception in thread “main” java.lang.StackOverflowError
Exception in thread “main” java.lang.OutOfMemoryError:unable to create new native
操作系统分配给每个进程的内存是有限制的,每个线程分配到的栈容量越大,可以建立的线程数量就越少。堆内存越大,栈内存就越小,能建立的线程数也越少,容易导致不能创建线程的问题。
运行时常量池溢出
Exception in thread “main” java.lang.OutOfMemoryError:PerGen space
Java对象在内存中划分
堆的分代
JVM堆一般分为三个部分:
Young:年轻代
Young区被划分为三部分,Eden区和两个大小严格相同的Survivor区,其中Survivor区间中,某一时刻只有其中一个是被使用的,另外一个留做垃圾收集时复制对象用,在Young区间变满的时候,minor GC就会将存活的对象移到空闲的Survivor区间中,根据JVM的策略,在经过几次垃圾收集后,任然存活于Survivor的对象将被移动到Tenured区间。
Tenured:年老代
Tenured区主要保存生命周期长的对象,一般是一些老的对象,当一些对象在Young复制转移一定的次数以后,对象就会被转移到Tenured区,一般如果系统中用了application级别的缓存,缓存中的对象往往会被转移到这一区间。
Perm:持久代
Perm代主要保存class,method,filed等对象,这部门的空间一般不会溢出
新生代中的Eden区域与Survivor区域的容量比为8:1
判断对象是否有引用
1、引用计数
在JDK1.2之前,使用的是引用计数器算法,即当这个类被加载到内存以后,就会产生方法区,堆栈、程序计数器等一系列信息,当创建对象的时候,为这个对象在堆栈空间中分配对象,同时会产生一个引用计数器,同时引用计数器+1,当有新的引用的时候,引用计数器继续+1,而当其中一个引用销毁的时候,引用计数器-1,当引用计数器被减为零的时候,标志着这个对象已经没有引用了,可以回收了!这种算法在JDK1.2之前的版本被广泛使用,但是随着业务的发展,很快出现了一个问题
当我们的代码出现下面的情形时,该算法将无法适应
a) ObjA.obj = ObjB
b) ObjB.obj – ObjA
2、根搜索算法
根搜索算法是从离散数学中的图论引入的,程序把所有的引用关系看作一张图,从一个节点GC ROOT开始,寻找对应的引用节点,找到这个节点以后,继续寻找这个节点的引用节点,当所有的引用节点寻找完毕之后,剩余的节点则被认为是没有被引用到的节点,即无用的节点。
垃圾回收算法
1、标记-清除算法
这种收集器首先遍历对象图并标记可到达的对象,然后扫描堆栈以寻找未标记对象并释放它们的内存。这种收集器一般使用单线程工作并停止其他操作。
标记-清除算法采用从根集合进行扫描,对存活的对象对象标记,标记完毕后,再扫描整个空间中未被标记的对象,进行回收,如上图所示。
标记-清除算法不需要进行对象的移动,并且仅对不存活的对象进行处理,在存活对象比较多的情况下极为高效,但由于标记-清除算法直接回收不存活的对象,因此会造成内存碎片!
2. 复制算法
它将可用内存容量划分为大小相等的两块,每次只使用其中的一块。当这一块用完之后,就将还存活的对象复制到另外一块上面,然后在把已使用过的内存空间一次理掉。这样使得每次都是对其中的一块进行内存回收,不会产生碎片等情况,只要移动堆订的指针,按顺序分配内存即可,实现简单,运行高效。
主要缺点:内存缩小为原来的一半。
3. 标记 - 整理算法
标记操作和“标记-清除”算法一致,后续操作不只是直接清理对象,而是在清理无用对象完成后让所有存活的对象都向一端移动,并更新引用其对象的指针。
主要缺点:在标记-清除的基础上还需进行对象的移动,成本相对较高,好处则是不会产生内存碎片。
JVM垃圾收集器
由于内存中的对象,是按存活周期存放在不同的内存块中的,所以,我们选择不同的算法来针对不同的内存块进行垃圾收集。从而,对于,不同的内存块,我们需要有不同的垃圾收集器。
新生代的垃圾收集器有:Serial收集器、ParNew收集器、Parallel Scavenge收集器
老年代的垃圾收集器有:Serial Old收集器、Parallel Old收集器、CMS收集器、G1收集器
下面我们来分别介绍一下这些垃圾收集器
Serial收集器/Serial Old收集器
Serial收集器/Serial Old收集器,是单线程的,使用“复制”算法。当它工作时,必须暂停其它所有工作线程。特点:简单而高效。对于运行在Client模式下的虚拟机来说是一个很好的选择。
ParNew收集器
ParNew收集器,是Serial收集器的多线程版。是运行在Server模式下的虚拟机中首选的新生代收集器。除了Serial收集器外,目前只有它能与CMS收集器配合工作。
Parallel Scavenge收集器/Parallel Old收集器
Parallel Scavenge收集器,也是使用“复制”算法的、并行的多线程收集器。这些都和ParNew收集器一样。但它关注的是吞吐量(CPU用于运行用户代码的时间与CPU总消耗时间的比值),而其它收集器(Serial/Serial Old、ParNew、CMS)关注的是垃圾收集时用户线程的停顿时间。
Parallel Old收集器是Parallel Scavenge收集器的老年代版本。
CMS收集器
CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器,使用“标记-清除”算法。
CMS收集器分4个步骤进行垃圾收集工作:
1、初始标记 2、并发标记 3、重新标记 4、并发清除
其中“初始标记”、“重新标记”是需要暂停其它所有工作线程的。
G1收集器
G1(Garbage First)收集器,基于“标记-整理”算法,可以非常精确地控制停顿。
四、数据库SQL语句优化
1、尽量避免在列上做运算,这样会导致索引失败。
SELECT * FROM t WHERE YEAR(d) >= 2011;
优化为:
SELECT * FROM t WHERE d>= ‘2011-01-01’;
2、注意like模糊查询的使用,避免 %%
3、使用批量插入语句节省交互
INSERT INTO t(id,name)VALUES(1,’a’);
INSERT INTO t(id,name)VALUES(2,’b’);
INSERT INTO t(id,name)VALUES(3,’c’);
优化为:
INSERT INTO t(id,name)VALUES(1,’a’),(2,’b’),(3,’c’);
4、limit的基数比较大的时候使用between,between限定比limit快,所以海量数据访问时,建议between或是where替换掉limit。但是between也有缺陷,如果id中间有断行或是中间部分id不读取的情况,总读取的数量会少于预计数量!
limit 1000 10
between 1000 1010
在取比较后面的数据时,通过desc方式把数据反向查找,以减少对前段数据的扫描,让limit的基数越小越好!
5、对查询进行优化,要尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引。
6、应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描,如:
select id from t where num is null
最好不要给数据库留NULL,尽可能的使用 NOT NULL填充数据库.
备注、描述、评论之类的可以设置为 NULL,其他的,最好不要使用NULL。
不要以为 NULL 不需要空间,比如:char(100) 型,在字段建立时,空间就固定了, 不管是否插入值(NULL也包含在内),都是占用 100个字符的空间的,如果是varchar这样的变长字段, null 不占用空间。
可以在num上设置默认值0,确保表中num列没有null值,然后这样查询:
select id from t where num = 0
7、应尽量避免在 where 子句中使用 != 或 <> 操作符,否则将引擎放弃使用索引而进行全表扫描。
8、应尽量避免在 where 子句中使用 or 来连接条件,如果一个字段有索引,一个字段没有索引,将导致引擎放弃使用索引而进行全表扫描,用UNION替换OR,用IN来替换OR如:
select id from t where num=10 or Name = 'admin'
可以这样查询:
select id from t where num = 10
union all
select id from t where Name = 'admin'
9、in 和 not in 也要慎用,因为用不到索引,否则会导致全表扫描,如:
select id from t where num in(1,2,3)
对于连续的数值,能用 between 就不要用 in 了:
select id from t where num between 1 and 3
很多时候用 exists 代替 in 是一个好的选择:
select num from a where num in(select num from b)
用下面的语句替换:
select num from a where exists(select 1 from b where num=a.num)
10、应尽量避免在 where 子句中对字段进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描。如:
select id from t where num/2 = 100
应改为:
select id from t where num = 100*2
11、不要在 where 子句中的“=”左边进行函数、算术运算或其他表达式运算,否则系统将可能无法正确使用索引,查询时要尽可能将操作移至等号右边。在一个表中设置索引列是为了提高表的检索速度,但是要对索引列的值进行计算,那么,检索这个表时索引列就会失去效果,也就是说,在查询数据时索引列不会起作用,而是对表进行了全表扫描。因此,当在查询语句中要对列进行计算时,就必须要看清楚再计算中是否使用了索引列,尽量避免使用索引列进行计算,这样就能达到优化查询语句的目的。
12、select count(*) from table;这样不带任何条件的count会引起全表扫描,并且没有任何业务意义,是一定要杜绝的。
Selct count(1) from table
13、关键字段建立索引。
索引并不是越多越好,索引固然可以提高相应的 select 的效率,但同时也降低了 insert 及 update 的效率,因为 insert 或 update 时有可能会重建索引,所以怎样建索引需要慎重考虑,视具体情况而定。一个表的索引数最好不要超过6个,若太多则应考虑一些不常使用到的列上建的索引是否有必要。
不能用null作索引,任何包含null值的列都将不会被包含在索引中。即使索引有多列这样的情况下,只要这些列中有一列含有null,该列就会从索引中排除。也就是说如果某列存在空值,即使对该列建索引也不会提高性能。任何在where子句中使用is null或is not null的语句优化器是不允许使用索引的。
14、尽量使用数字型字段,若只含数值信息的字段尽量不要设计为字符型,这会降低查询和连接的性能,并会增加存储开销。这是因为引擎在处理查询和连 接时会逐个比较字符串中每一个字符,而对于数字型而言只需要比较一次就够了。
15、任何地方都不要使用 select * from t ,用具体的字段列表代替“*”,不要返回用不到的任何字段。说明:SQL查询语句执行步骤:首先执行FROM子句,形成一个中间表(临时表);然后执行WHERE子句,去除不满足该关系的记录;最后执行SELECT子句,提取(或替代“*”)所需要的字段。
16、尽量避免使用游标,因为游标的效率较差,如果游标操作的数据超过1万行,那么就应该考虑改写。
17、使用存储过程,它使SQL变得更加灵活和高效。
18、EXISTS要远比IN的效率高。里面关系到full table scan和range scan。几乎将所有的IN操作符子查询改写为使用EXISTS的子查询。用EXISTS替代IN、用NOT EXISTS替代NOT IN:
19、在海量查询时尽量少用格式转换。
20、IN、OR子句常会使用工作表,使索引失效。如果不产生大量重复值,可以考虑把子句拆开。拆开的子句中应该包含索引。
21、多表查询时多使用别名
实现多表查询时,应该考虑使用别名代替原表名,并在查询字段前面加上别名作为前缀,这样Oracle数据库在执行这样的查询语句时,就能很清楚地判断出哪些字段来自于哪个表,减少不必要的解析时间。
说明:在Oracle数据中设置表的别名方式主要有下面两种:“SELECT * FROM 表名 别名”;“SELECT * FROM 表名 AS 别名”。除了多表查询时给表设置别名外,在表的顺序上也要注意表的关联关系,因为在Oracle数据库中,查询时也是按表出现的顺序进行链接的。
22、条件查询多使用WHERE,避免使用HAVING。在SQL语言中指定查询条件时主要有两个关键词:一个是WHERE子句,另一个是HAVING子句。但是在实际的应用中,应该尽量避免使用HAVING子句,因为HAVING子句通常用于在检索出的结果集中过滤结果,所以效率会比较底。使用WHERE子句可以在查询时就限定HAVING这样就可以提高查询速度。
a. 避免使用HAVING子句, HAVING 只会在检索出所有记录之后才对结果集进行过滤. 这个处理需要排序,总计等操作.
b. 如果能通过WHERE子句限制记录的数目,那就能减少这方面的开销.
c. (非oracle中)on、where、having这三个都可以加条件的子句中,on是最先执行,where次之,having最后,
d. 因为on是先把不符合条件的记录过滤后才进行统计,它就可以减少中间运算要处理的数据,按理说应该速度是最快的,where也应该比having快点的,因为它过滤数据后才进行sum,在两个表联接时才用on的,所以在一个表的时候,就剩下where跟having比较了。
e. 在这单表查询统计的情况下,如果要过滤的条件没有涉及到要计算字段,那它们的结果是一样的,只是where可以使用rushmore技术,而having就不能,在速度上后者要慢如果要涉及到计算的字段,就表示在没计算之前,这个字段的值是不确定的,根据上篇写的工作流程,where的作用时间是在计算之前就完成的,而having就是在计算后才起作用的,所以在这种情况下,两者的结果会不同。
f. 在多表联接查询时,on比where更早起作用。系统首先根据各个表之间的联接条件,把多个表合成一个临时表后,再由where进行过滤,然后再计算,计算完后再由having进行过滤。
g. 由此可见,要想过滤条件起到正确的作用,首先要明白这个条件应该在什么时候起作用,然后再决定放在那里
23、用“>=”替代“>”(“<=”替代“<”)
高效: SELECT * FROM EMP WHERE DEPTNO >=4
低效: SELECT * FROM EMP WHERE DEPTNO > 3
两者的区别在于,前者DBMS将直接跳到第一个DEPTNO等于4的记录而后者将首先定位到DEPTNO=3的记录并且向前扫描到第一个DEPTNO大于3的记录。
24、使用大写字母代替小写字母
在编写SQL语句时,虽然在数据库中是不区分大小写的,但是在执行SQL语句时,数据库首先会把语句中所有的小写字母转换成大写字母。所以,在编写SQL语句时尽量使用大写字母,这样能够提高数据库识别语句的速度。
25、尽量不使用不等号(“!=”,“<>")
在进行比较时经常会用到不等号,不等号有两种写法,即“!=”和“<>”。使用不等号就相当于不会使用到索引,也就是说,使用不等号会对整个表进行扫描,这就降低了查询效率。所以,尽可能使用其他方式代替不等号,比如可以使用“>”或“〈”号代替。
26、使用UNION ALL关键词, 慎用union关键字
UNION在进行表链接后会筛选掉重复的记录,所以在表链接后会对所产生的结果集进行排序运算,删除重复的记录再返回结果,如果表数据量大的话可能会导致用磁盘进行排序。实际大部分应用中是不会产生重复的记录,所以采用UNION ALL操作符替代UNION,因为UNION ALL操作只是简单的将两个结果合并后就返回。
27、Oracle从下到上处理Where子句中的多个查询条件,所以表连接语句应写在其他WHERE条件前,可以过滤掉最大数量记录的条件必须写在WHERE子句的末尾。
28、慎用distinct关键字
distinct在查询一个字段或者很少字段的情况下使用,会避免重复数据的出现,给查询带来优化效果。
但是查询字段很多的情况下使用,则会大大降低查询效率
29、用WHERE替代ORDER BY:
ORDER BY 子句只在两种严格的条件下使用索引.
ORDER BY中所有的列必须包含在相同的索引中并保持在索引中的排列顺序.
ORDER BY中所有的列必须定义为非空.
WHERE子句使用的索引和ORDER BY子句中所使用的索引不能并列.
例如:
表DEPT包含以下列:
DEPT_CODE PK NOT NULL
DEPT_DESC NOT NULL
DEPT_TYPE NULL
低效: (索引不被使用)
SELECT DEPT_CODE FROM DEPT ORDER BY DEPT_TYPE
高效: (使用索引)
SELECT DEPT_CODE FROM DEPT WHERE DEPT_TYPE > 0
30、:
某些SELECT 语句中的WHERE子句不使用索引. 这里有一些例子.
在下面的例子里,
‘!=' 将不使用索引. 记住, 索引只能告诉你什么存在于表中, 而不能告诉你什么不存在于表中.
‘||’是字符连接函数. 就象其他函数那样, 停用了索引.
‘+’是数学函数. 就象其他数学函数那样, 停用了索引.
相同的索引列不能互相比较,这将会启用全表扫描.
31、优化GROUP BY:
提高GROUP BY 语句的效率, 可以通过将不需要的记录在GROUP BY 之前过滤掉.下面两个查询返回相同结果但第二个明显就快了许多.
低效:
SELECT JOB , AVG(SAL)
FROM EMP
GROUP JOB
HAVING JOB = ‘PRESIDENT'
OR JOB = ‘MANAGER'
高效:
SELECT JOB , AVG(SAL)
FROM EMP
WHERE JOB = ‘PRESIDENT'
OR JOB = ‘MANAGER'
GROUP JOB
工作中的术语
开发机
测试机
生产机
数据割接
流水编号