要正确的优化SQL,我们需要快速定位能性的瓶颈点,也就是说快速找到我们SQL主要的开销在哪里?而大多数情况性能最慢的设备会是瓶颈点,如下载时网络速度可能会是瓶颈点,本地复制文件时硬盘可能会是瓶颈点,为什么这些一般的工作我们能快速确认瓶颈点呢,因为我们对这些慢速设备的性能数据有一些基本的认识,如网络带宽是2Mbps,硬盘是每分钟7200转等等。因此,为了快速找到SQL的性能瓶颈点,我们也需要了解我们计算机系统的硬件基本性能指标,下图展示的当前主流计算机性能指标数据。
从图上可以看到基本上每种设备都有两个指标:
带宽(吞吐量):代表硬件持续处理能力。
从上图可以看出,计算机系统硬件性能从高到代依次为:
CPU——Cache(L1-L2-L3)——内存——SSD硬盘——网络——硬盘
CPU及内存:缓存数据访问、比较、排序、事务检测、SQL解析、函数或逻辑运算;
网络:结果数据传输、SQL请求、远程数据库访问(dblink);
根据当前计算机硬件的基本性能指标及其在数据库中主要操作内容,可以整理出如下图所示的性能基本优化法则:
这个优化法则归纳为5个层次:
1、减少数据访问(减少磁盘访问)
2、返回更少数据(减少网络传输或磁盘访问)
3、减少交互次数(减少网络传输)
4、减少服务器CPU开销(减少CPU及内存开销)
5、利用更多资源(增加资源)
由于每一层优化法则都是解决其对应硬件的性能问题,所以带来的性能提升比例也不一样。传统数据库系统设计是也是尽可能对低速设备提供优化方法,因此针对低速设备问题的可优化手段也更多,优化成本也更低。我们任何一个SQL的性能优化都应该按这个规则由上到下来诊断问题并提出解决方案,而不应该首先想到的是增加资源解决问题。
以下是每个优化法则层级对应优化效果及成本经验参考:
优化法则
性能提升效果
优化成本
减少数据访问
1~1000
低
返回更少数据
1~100
减少交互次数
1~20
减少服务器CPU开销
1~5
利用更多资源
@~10
高
接下来,我们针对5种优化法则列举常用的优化手段并结合实例分析。
ROWID是每条记录在数据库中的唯一标识,通过ROWID可以直接定位记录到对应的文件号及数据块位置。ROWID内容包括文件号、对像号、数据块号、记录槽号,如下图所示:
数据库索引的原理非常简单,但在复杂的表中真正能正确使用索引的人很少,即使是专业的DBA也不一定能完全做到最优。
索引会大大增加表记录的DML(INSERT,UPDATE,DELETE)开销,正确的索引可以让性能提升100,1000倍以上,不合理的索引也可能会让性能下降100倍,因此在一个表中创建什么样的索引需要平衡各种业务需求。
索引常见问题:
索引有哪些种类?
常见的索引有B-TREE索引、位图索引、全文索引,位图索引一般用于数据仓库应用,全文索引由于使用较少,这里不深入介绍。B-TREE索引包括很多扩展类型,如组合索引、反向索引、函数索引等等,以下是B-TREE索引的简单介绍:
B-TREE索引也称为平衡树索引(BalanceTree),它是一种按字段排好序的树形目录结构,主要用于提升查询性能和唯一约束支持。B-TREE索引的内容包括根节点、分支节点、叶子节点。
叶子节点内容:索引字段内容+表记录ROWID
根节点,分支节点内容:当一个数据块中不能放下所有索引字段数据时,就会形成树形的根节点或分支节点,根节点与分支节点保存了索引树的顺序及各层级间的引用关系。
一个普通的BTREE索引结构示意图如下所示:
如果我们把一个表的内容认为是一本字典,那索引就相当于字典的目录,如下图所示:
图中是一个字典按部首+笔划数的目录,相当于给字典建了一个按部首+笔划的组合索引。
一个表中可以建多个索引,就如一本字典可以建多个目录一样(按拼音、笔划、部首等等)。
一个索引也可以由多个字段组成,称为组合索引,如上图就是一个按部首+笔划的组合目录。
SQL什么条件会使用索引?
当字段上建有索引时,通常以下情况会使用索引:
INDEX_COLUMN=
INDEX_COLUMN>
INDEX_COLUMN>=
INDEX_COLUMN<
INDEX_COLUMN<=
INDEX_COLUMNbetweenand
INDEX_COLUMNin(,,...,)
INDEX_COLUMNlike||'%'(后导模糊查询)
T1.INDEX_COLUMN=T2.COLUMN1(两个表通过索引字段关联)
SQL什么条件不会使用索引?
查询条件
不能使用索引原因
INDEX_COLUMN<>
INDEX_COLUMNnotin(,,...,)
不等于操作不能使用索引
function(INDEX_COLUMN)=
INDEX_COLUMN+1=
INDEX_COLUMN||'a'=
经过普通运算或函数运算后的索引字段不能使用索引
INDEX_COLUMNlike'%'||
INDEX_COLUMNlike'%'||||'%'
含前导模糊查询的Like语法不能使用索引
INDEX_COLUMNisnull
B-TREE索引里不保存字段为NULL值记录,因此ISNULL不能使用索引
NUMBER_INDEX_COLUMN='12345'
CHAR_INDEX_COLUMN=12345
Oracle在做数值比较时需要将两边的数据转换成同一种数据类型,如果两边数据类型不同时会对字段值隐式转换,相当于加了一层函数处理,所以不能使用索引。
a.INDEX_COLUMN=a.COLUMN_1
给索引查询的值应是已知数据,不能是未知字段值。
注:
经过函数运算字段的字段要使用可以使用函数索引,这种需求建议与DBA沟通。
有时候我们会使用多个字段的组合索引,如果查询条件中第一个字段不能使用索引,那整个查询也不能使用索引
如:我们company表建了一个id+name的组合索引,以下SQL是不能使用索引的
Select*fromcompanywherename=
Oracle9i后引入了一种indexskipscan的索引方式来解决类似的问题,但是通过indexskipscan提高性能的条件比较特殊,使用不好反而性能会更差。
我们一般在什么字段上建索引?
这是一个非常复杂的话题,需要对业务及数据充分分析后再能得出结果。主键及外键通常都要有索引,其它需要建索引的字段应满足以下条件:
1、字段出现在查询条件中,并且查询条件可以使用索引;
2、语句执行频率高,一天会有几千次以上;
3、通过字段条件可筛选的记录集很小,那数据筛选比例是多少才适合?
这个没有固定值,需要根据表数据量来评估,以下是经验公式,可用于快速评估:
小表(记录数小于10000行的表):筛选比例<10%;
大表:(筛选返回记录数)<(表总记录数*单条记录长度)/10000/16
单条记录长度≈字段平均内容长度之和+字段数*2
以下是一些字段是否需要建B-TREE索引的经验分类:
字段类型
常见字段名
需要建索引的字段
主键
ID,PK
外键
PRODUCT_ID,COMPANY_ID,MEMBER_ID,ORDER_ID,TRADE_ID,PAY_ID
有对像或身份标识意义字段
HASH_CODE,USERNAME,IDCARD_NO,EMAIL,TEL_NO,IM_NO
索引慎用字段,需要进行数据分布及使用场景详细评估
日期
GMT_CREATE,GMT_MODIFIED
年月
YEAR,MONTH
状态标志
PRODUCT_STATUS,ORDER_STATUS,IS_DELETE,VIP_FLAG
类型
ORDER_TYPE,IMAGE_TYPE,GENDER,CURRENCY_TYPE
区域
COUNTRY,PROVINCE,CITY
操作人员
CREATOR,AUDITOR
数值
LEVEL,AMOUNT,SCORE
长字符
ADDRESS,COMPANY_NAME,SUMMARY,SUBJECT
不适合建索引的字段
描述备注
DESCRIPTION,REMARK,MEMO,DETAIL
大字段
FILE_CONTENT,EMAIL_CONTENT
如何知道SQL是否使用了正确的索引?
索引对DML(INSERT,UPDATE,DELETE)附加的开销有多少?
索引对于Insert性能降低56%
索引对于Update性能降低47%
索引对于Delete性能降低29%
因此对于写IO压力比较大的系统,表的索引需要仔细评估必要性,另外索引也会占用一定的存储空间。
有些时候,我们只是访问表中的几个字段,并且字段内容较少,我们可以为这几个字段单独建立一个组合索引,这样就可以直接只通过访问索引就能得到数据,一般索引占用的磁盘空间比表小很多,所以这种方式可以大大减少磁盘IO开销。
如:selectid,namefromcompanywheretype='2';
如果这个SQL经常使用,我们可以在type,id,name上创建组合索引
createindexmy_comb_indexoncompany(type,id,name);
有了这个组合索引后,SQL就可以直接通过my_comb_index索引返回数据,不需要访问company表。
还是拿字典举例:有一个需求,需要查询一本汉语字典中所有汉字的个数,如果我们的字典没有目录索引,那我们只能从字典内容里一个一个字计数,最后返回结果。如果我们有一个拼音目录,那就可以只访问拼音目录的汉字进行计数。如果一本字典有1000页,拼音目录有20页,那我们的数据访问成本相当于全表访问的50分之一。
切记,性能优化是无止境的,当性能可以满足需求时即可,不要过度优化。在实际数据库中我们不可能把每个SQL请求的字段都建在索引里,所以这种只通过索引访问数据的方法一般只用于核心应用,也就是那种对核心表访问量最高且查询字段数据量很少的查询。
目前ORACLE有SQL执行计划的算法约300种,而且一直在增加,所以SQL执行计划是一个非常复杂的课题,一个普通DBA能掌握50种就很不错了,就算是资深DBA也不可能把每个执行计划的算法描述清楚。虽然有这么多种算法,但并不表示我们无法优化执行计划,因为我们常用的SQL执行计划算法也就十几个,如果一个程序员能把这十几个算法搞清楚,那就掌握了80%的SQL执行计划调优知识。
由于篇幅的原因,SQL执行计划需要专题介绍,在这里就不多说了。
一般数据分页方式有:
将数据从应用服务器全部下载到本地应用程序或浏览器,在应用程序或浏览器内部通过本地代码进行分页处理
优点:编码简单,减少客户端与应用服务器网络交互次数
适应场景:客户端与应用服务器网络延时较大,但要求后续操作流畅,如手机GPRS,超远程访问(跨国)等等。
Listlist=executeQuery(“select*fromemployeeorderbyid”);
Intcount=list.size();
ListsubList=list.subList(10,20);
优点:编码简单,只需要一次SQL交互,总数据与分页数据差不多时性能较好。
缺点:总数据量较多时性能较差。
适应场景:数据库系统不支持分页处理,数据量较小并且可控。
采用数据库SQL分页需要两次SQL完成
一个SQL计算总数量
一个SQL返回分页后的数据
优点:性能好
缺点:编码复杂,各种数据库语法不同,需要两次SQL交互。
oracle数据库一般采用rownum来进行分页,常用分页语法有如下两种:
直接通过rownum分页:
select*from(
selecta.*,rownumrnfrom
(select*fromproductawherecompany_id=orderbystatus)a
whererownum<=20)
wherern>10;
数据访问开销=索引IO+索引全部记录结果对应的表数据IO
采用rowid分页语法
优化原理是通过纯索引找出分页记录的ROWID,再通过ROWID回表返回数据,要求内层查询和排序字段全在索引里。
createindexmyindexonproduct(company_id,status);
selectb.*from(
(selectrowidrid,statusfromproductawherecompany_id=orderbystatus)a
wherern>10)a,productb
wherea.rid=b.rowid;
数据访问开销=索引IO+索引分页结果对应的表数据IO
实例:
一个公司产品有1000条记录,要分页取其中20个产品,假设访问公司索引需要50个IO,2条记录需要1个表数据IO。
那么按第一种ROWNUM分页写法,需要550(50+1000/2)个IO,按第二种ROWID分页写法,只需要60个IO(50+20/2);
通过去除不必要的返回字段可以提高性能,例:
调整前:select*fromproductwherecompany_id=;
调整后:selectid,namefromproductwherecompany_id=;
优点:
1、减少数据在网络上传输开销
2、减少服务器数据处理开销
3、减少客户端内存占用
4、字段变更时提前发现问题,减少程序BUG
5、如果访问的所有字段刚好在一个索引里面,则可以使用纯索引访问提高性能。
缺点:增加编码工作量
由于会增加一些编码工作量,所以一般需求通过开发规范来要求程序员这么做,否则等项目上线后再整改工作量更大。
如果你的查询表中有大字段或内容较多的字段,如备注信息、文件内容等等,那在查询表时一定要注意这方面的问题,否则可能会带来严重的性能问题。如果表经常要查询并且请求大内容字段的概率很低,我们可以采用分表处理,将一个大表分拆成两个一对一的关系表,将不常用的大内容字段放在一张单独的表中。如一张存储上传文件的表:
T_FILE(ID,FILE_NAME,FILE_SIZE,FILE_TYPE,FILE_CONTENT)
我们可以分拆成两张一对一的关系表:
T_FILE(ID,FILE_NAME,FILE_SIZE,FILE_TYPE)
T_FILECONTENT(ID,FILE_CONTENT)
通过这种分拆,可以大大提少T_FILE表的单条记录及总大小,这样在查询T_FILE时性能会更好,当需要查询FILE_CONTENT字段内容时再访问T_FILECONTENT表。
数据库访问框架一般都提供了批量提交的接口,jdbc支持batch的提交处理方法,当你一次性要往一个表中插入1000万条数据时,如果采用普通的executeUpdate处理,那么和服务器交互次数为1000万次,按每秒钟可以向数据库服务器提交10000次估算,要完成所有工作需要1000秒。如果采用批量提交模式,1000条提交一次,那么和服务器交互次数为1万次,交互次数大大减少。采用batch操作一般不会减少很多数据库服务器的物理IO,但是会大大减少客户端与服务端的交互次数,从而减少了多次发起的网络延时开销,同时也会降低数据库的CPU开销。
假设要向一个普通表插入1000万数据,每条记录大小为1K字节,表上没有任何索引,客户端与数据库服务器网络是100Mbps,以下是根据现在一般计算机能力估算的各种batch大小性能对比值:
单位:ms
Nobatch
Batch=10
Batch=100
Batch=1000
Batch=10000
0.1
0.02
0.2
2
20
200
0.01
1
10
100
小计
0.23
0.5
3.2
30.2
300.2
0.05
0.032
0.0302
0.03002
从上可以看出,Insert操作加大Batch可以对性能提高近8倍性能,一般根据主键的Update或Delete操作也可能提高2-3倍性能,但不如Insert明显,因为Update及Delete操作可能有比较大的开销在物理IO访问。以上仅是理论计算值,实际情况需要根据具体环境测量。
很多时候我们需要按一些ID查询数据库记录,我们可以采用一个ID一个请求发给数据库,如下所示:
for:varinids[]dobegin
select*frommytablewhereid=:var;
end;
我们也可以做一个小的优化,如下所示,用IDINLIST的这种方式写SQL:
select*frommytablewhereidin(:id1,id2,...,idn);
通过这样处理可以大大减少SQL请求的数量,从而提高性能。那如果有10000个ID,那是不是全部放在一条SQL里处理呢?答案肯定是否定的。首先大部份数据库都会有SQL长度和IN里个数的限制,如ORACLE的IN里就不允许超过1000个值。
另外当前数据库一般都是采用基于成本的优化规则,当IN数量达到一定值时有可能改变SQL执行计划,从索引访问变成全表访问,这将使性能急剧变化。随着SQL中IN的里面的值个数增加,SQL的执行计划会更复杂,占用的内存将会变大,这将会增加服务器CPU及内存成本。
评估在IN里面一次放多少个值还需要考虑应用服务器本地内存的开销,有并发访问时要计算本地数据使用周期内的并发上限,否则可能会导致内存溢出。
综合考虑,一般IN里面的值个数超过20个以后性能基本没什么太大变化,也特别说明不要超过100,超过后可能会引起执行计划的不稳定性及增加数据库CPU及内存成本,这个需要专业DBA评估。
以下是jdbc测试的代码,采用本地数据库,表缓存在数据库CACHE中,因此没有网络连接及磁盘IO开销,客户端只遍历游标,不做任何处理,这样更能体现fetch参数的影响:
Stringvsql="select*fromt_employee";
PreparedStatementpstmt=conn.prepareStatement(vsql,ResultSet.TYPE_FORWARD_ONLY,ResultSet.CONCUR_READ_ONLY);
pstmt.setFetchSize(1000);
ResultSetrs=pstmt.executeQuery(vsql);
intcnt=rs.getMetaData().getColumnCount();
Objecto;
while(rs.next()){
for(inti=1;i<=cnt;i++){
o=rs.getObject(i);
}
测试示例中的employee表有100000条记录,每条记录平均长度135字节
以下是测试结果,对每种fetchsize测试5次再取平均值:
fetchsize
elapse_time(s)
20.516
11.34
4
6.894
8
4.65
16
3.584
32
2.865
64
2.656
128
2.44
256
2.765
512
3.075
1024
2.862
2048
2.722
4096
2.681
8192
2.715
Oraclejdbcfetchsize默认值为10,由上测试可以看出fetchsize对性能影响还是比较大的,但是当fetchsize大于100时就基本上没有影响了。fetchsize并不会存在一个最优的固定值,因为整体性能与记录集大小及硬件平台有关。根据测试结果建议当一次性要取大量数据时这个值设置为100左右,不要小于40。注意,fetchsize不能设置太大,如果一次取出的数据大于JVM的内存会导致内存溢出,所以建议不要超过1000,太大了也没什么性能提高,反而可能会增加内存溢出的危险。
iBatis的SqlMapping配置文件可以对每个SQL语句指定fetchsize大小,如下所示:
select*fromemployee
大型数据库一般都支持存储过程,合理的利用存储过程也可以提高系统性能。如你有一个业务需要将A表的数据做一些加工然后更新到B表中,但是又不可能一条SQL完成,这时你需要如下3步操作:
a:将A表数据全部取出到客户端;
b:计算出要更新的数据;
c:将计算结果更新到B表。
如果采用存储过程你可以将整个业务逻辑封装在存储过程里,然后在客户端直接调用存储过程处理,这样可以减少网络交互的成本。
当然,存储过程也并不是十全十美,存储过程有以下缺点:
a、不可移植性,每种数据库的内部编程语法都不太相同,当你的系统需要兼容多种数据库时最好不要用存储过程。
b、学习成本高,DBA一般都擅长写存储过程,但并不是每个程序员都能写好存储过程,除非你的团队有较多的开发人员熟悉写存储过程,否则后期系统维护会产生问题。
d、存储过程和常用应用程序语言不一样,它支持的函数及语法有可能不能满足需求,有些逻辑就只能通过应用程序处理。
e、如果存储过程中有复杂运算的话,会增加一些数据库服务端的处理成本,对于集中式数据库可能会导致系统可扩展性问题。
f、为了提高性能,数据库会把存储过程代码编译成中间运行代码(类似于java的class文件),所以更像静态语言。当存储过程引用的对像(表、视图等等)结构改变后,存储过程需要重新编译才能生效,在24*7高并发应用场景,一般都是在线变更结构的,所以在变更的瞬间要同时编译存储过程,这可能会导致数据库瞬间压力上升引起故障(Oracle数据库就存在这样的问题)。
个人观点:普通业务逻辑尽量不要使用存储过程,定时性的ETL任务或报表统计函数可以根据团队资源情况采用存储过程处理。
要通过优化业务逻辑来提高性能是比较困难的,这需要程序员对所访问的数据及业务流程非常清楚。
举一个案例:
某移动公司推出优惠套参,活动对像为VIP会员并且2010年1,2,3月平均话费20元以上的客户。
那我们的检测逻辑为:
selectavg(money)asavg_moneyfrombillwherephone_no='13988888888'anddatebetween'201001'and'201003';
selectvip_flagfrommemberwherephone_no='13988888888';
ifavg_money>20andvip_flag=truethen
begin
执行套参();
如果我们修改业务逻辑为:
ifavg_money>20then
ifvip_flag=truethen
通过这样可以减少一些判断vip_flag的开销,平均话费20元以下的用户就不需要再检测是否VIP了。
如果程序员分析业务,VIP会员比例为1%,平均话费20元以上的用户比例为90%,那我们改成如下:
这样就只有1%的VIP会员才会做检测平均话费,最终大大减少了SQL的交互次数。
以上只是一个简单的示例,实际的业务总是比这复杂得多,所以一般只是高级程序员更容易做出优化的逻辑,但是我们需要有这样一种成本优化的意识。
现在大部分Java框架都是通过jdbc从数据库取出数据,然后装载到一个list里再处理,list里可能是业务Object,也可能是hashmap。
由于JVM内存一般都小于4G,所以不可能一次通过sql把大量数据装载到list里。为了完成功能,很多程序员喜欢采用分页的方法处理,如一次从数据库取1000条记录,通过多次循环搞定,保证不会引起JVMOutofmemory问题。
以下是实现此功能的代码示例,t_employee表有10万条记录,设置分页大小为1000:
d1=Calendar.getInstance().getTime();
vsql="selectcount(*)cntfromt_employee";
pstmt=conn.prepareStatement(vsql);
ResultSetrs=pstmt.executeQuery();
Integercnt=0;
cnt=rs.getInt("cnt");
Integerlastid=0;
Integerpagesize=1000;
System.out.println("cnt:"+cnt);
Stringvsql="selectcount(*)cntfromt_employee";
PreparedStatementpstmt=conn.prepareStatement(vsql);
for(inti=0;i<=cnt/pagesize;i++){
vsql="select*from(select*fromt_employeewhereid>orderbyid)whererownum<=";
pstmt.setInt(1,lastid);
pstmt.setInt(2,pagesize);
rs=pstmt.executeQuery();
intcol_cnt=rs.getMetaData().getColumnCount();
for(intj=1;j<=col_cnt;j++){
o=rs.getObject(j);
lastid=rs.getInt("id");
rs.close();
pstmt.close();
很多持久层框架为了尽量让程序员使用方便,封装了jdbc通过statement执行数据返回到resultset的细节,导致程序员会想采用分页的方式处理问题。实际上如果我们采用jdbc原始的resultset游标处理记录,在resultset循环读取的过程中处理记录,这样就可以一次从数据库取出所有记录。显著提高性能。
这里需要注意的是,采用resultset游标处理记录时,应该将游标的打开方式设置为FORWARD_READONLY模式(ResultSet.TYPE_FORWARD_ONLY,ResultSet.CONCUR_READ_ONLY),否则会把结果缓存在JVM里,造成JVMOutofmemory问题。
代码示例:
pstmt.setFetchSize(100);
从测试结果可以看出性能提高了1倍多,如果采用分页模式数据库每次还需发生磁盘IO的话那性能可以提高更多。
iBatis等持久层框架考虑到会有这种需求,所以也有相应的解决方案,在iBatis里我们不能采用queryForList的方法,而应用该采用queryWithRowHandler加回调事件的方式处理,如下所示:
MyRowHandlermyrh=newMyRowHandler();
sqlmap.queryWithRowHandler("getAllEmployee",myrh);
classMyRowHandlerimplementsRowHandler{
publicvoidhandleRow(Objecto){
//todosomething
iBatis的queryWithRowHandler很好的封装了resultset遍历的事件处理,效果及性能与resultset遍历一样,也不会产生JVM内存溢出。
绑定变量是指SQL中对变化的值采用变量参数的形式提交,而不是在SQL中直接拼写对应的值。
非绑定变量写法:Select*fromemployeewhereid=1234567
绑定变量写法:
Select*fromemployeewhereid=
Preparestatement.setInt(1,1234567)
Java中Preparestatement就是为处理绑定变量提供的对像,绑定变量有以下优点:
1、防止SQL注入
2、提高SQL可读性
3、提高SQL解析性能,不使用绑定变更我们一般称为硬解析,使用绑定变量我们称为软解析。
第1和第2点很好理解,做编码的人应该都清楚,这里不详细说明。关于第3点,到底能提高多少性能呢,下面举一个例子说明:
假设有这个这样的一个数据库主机:
2个4核CPU
100块磁盘,每个磁盘支持IOPS为160
业务应用的SQL如下:
select*fromtablewherepk=
这个SQL平均4个IO(3个索引IO+1个数据IO)
IO缓存命中率75%(索引全在内存中,数据需要访问磁盘)
SQL硬解析CPU消耗:1ms(常用经验值)
SQL软解析CPU消耗:0.02ms(常用经验值)
是否使用绑定变量
CPU支持最大并发数
磁盘IO支持最大并发数
不使用
2*4*1000=8000
100*160=16000
使用
2*4*1000/0.02=400000
从以上计算可以看出,不使用绑定变量的系统当并发达到8000时会在CPU上产生瓶颈,当使用绑定变量的系统当并行达到16000时会在磁盘IO上产生瓶颈。所以如果你的系统CPU有瓶颈时请先检查是否存在大量的硬解析操作。
使用绑定变量为何会提高SQL解析性能,这个需要从数据库SQL执行原理说明,一条SQL在Oracle数据库中的执行过程如下图所示:
当一条SQL发送给数据库服务器后,系统首先会将SQL字符串进行hash运算,得到hash值后再从服务器内存里的SQL缓存区中进行检索,如果有相同的SQL字符,并且确认是同一逻辑的SQL语句,则从共享池缓存中取出SQL对应的执行计划,根据执行计划读取数据并返回结果给客户端。
如果在共享池中未发现相同的SQL则根据SQL逻辑生成一条新的执行计划并保存在SQL缓存区中,然后根据执行计划读取数据并返回结果给客户端。
为了更快的检索SQL是否在缓存区中,首先进行的是SQL字符串hash值对比,如果未找到则认为没有缓存,如果存在再进行下一步的准确对比,所以要命中SQL缓存区应保证SQL字符是完全一致,中间有大小写或空格都会认为是不同的SQL。
如果我们不采用绑定变量,采用字符串拼接的模式生成SQL,那么每条SQL都会产生执行计划,这样会导致共享池耗尽,缓存命中率也很低。
一些不使用绑定变量的场景:
b、数据分布不均匀的特殊逻辑,如产品表,记录有1亿,有一产品状态字段,上面建有索引,有审核中,审核通过,审核未通过3种状态,其中审核通过9500万,审核中1万,审核不通过499万。
要做这样一个查询:
selectcount(*)fromproductwherestatus=
对于这种情况应该不使用绑定变量,而直接采用字符拼接的方式生成SQL,这样可以为每个SQL生成不同的执行计划,如下所示。
selectcount(*)fromproductwherestatus='approved';//不使用索引
selectcount(*)fromproductwherestatus='tbd';//不使用索引
selectcount(*)fromproductwherestatus='auditing';//使用索引
以下列出了可能会发生排序操作的SQL语法:
Orderby
Groupby
Distinct
Exists子查询
NotExists子查询
In子查询
NotIn子查询
Union(并集),UnionAll也是一种并集操作,但是不会发生排序,如果你确认两个数据集不需要执行去除重复数据操作,那请使用UnionAll代替Union。
Minus(差集)
Intersect(交集)
CreateIndex
MergeJoin,这是一种两个表连接的内部算法,执行时会把两个表先排序好再连接,应用于两个大表连接的操作。如果你的两个表连接的条件都是等值运算,那可以采用HashJoin来提高性能,因为HashJoin使用Hash运算来代替排序的操作。具体原理及设置参考SQL执行计划优化专题。
我们SQL的业务逻辑经常会包含一些比较操作,如a=b,a
Like模糊查询,如下所示:
alike‘%abc%’
Like模糊查询对于数据库来说不是很擅长,特别是你需要模糊检查的记录有上万条以上时,性能比较糟糕,这种情况一般可以采用专用Search或者采用全文索引方案来提高性能。
不能使用索引定位的大量InList,如下所示:
ain(:1,:2,:3,…,:n)----n>20
如果这里的a字段不能通过索引比较,那数据库会将字段与in里面的每个值都进行比较运算,如果记录数有上万以上,会明显感觉到SQL的CPU开销加大,这个情况有两种解决方式:
a、将in列表里面的数据放入一张中间小表,采用两个表HashJoin关联的方式处理;
b、采用str2varList方法将字段串列表转换一个临时表处理,关于str2varList方法可以在网上直接查询,这里不详细介绍。
以上两种解决方案都需要与中间表HashJoin的方式才能提高性能,如果采用了NestedLoop的连接方式性能会更差。
如果发现我们的系统IO没问题但是CPU负载很高,就有可能是上面的原因,这种情况不太常见,如果遇到了最好能和DBA沟通并确认准确的原因。
什么是复杂运算,一般我认为是一秒钟CPU只能做10万次以内的运算。如含小数的对数及指数运算、三角函数、3DES及BASE64数据加密算法等等。
如果有大量这类函数运算,尽量放在客户端处理,一般CPU每秒中也只能处理1万-10万次这样的函数运算,放在数据库内不利于高并发处理。
多进程并行访问是指在客户端创建多个进程(线程),每个进程建立一个与数据库的连接,然后同时向数据库提交访问请求。当数据库主机资源有空闲时,我们可以采用客户端多进程并行访问的方法来提高性能。如果数据库主机已经很忙时,采用多进程并行访问性能不会提高,反而可能会更慢。所以使用这种方式最好与DBA或系统管理员进行沟通后再决定是否采用。
例如:
那是不是并行数越多越好呢,开1000个并行是否只要50ms就搞定,答案肯定是否定的,当并行数超过服务器主机资源的上限时性能就不会再提高,如果再增加反而会增加主机的进程间调度成本和进程冲突机率。
以下是一些如何设置并行数的基本建议:
如果瓶颈在服务器主机,但是主机还有空闲资源,那么最大并行数取主机CPU核数和主机提供数据服务的磁盘数两个参数中的最小值,同时要保证主机有资源做其它任务。
如果瓶颈在客户端处理,但是客户端还有空闲资源,那建议不要增加SQL的并行,而是用一个进程取回数据后在客户端起多个进程处理即可,进程数根据客户端CPU核数计算。
如果瓶颈在客户端网络,那建议做数据压缩或者增加多个客户端,采用mapreduce的架构处理。
如果瓶颈在服务器网络,那需要增加服务器的网络带宽或者在服务端将数据压缩后再处理了。
数据库并行处理是指客户端一条SQL的请求,数据库内部自动分解成多个进程并行处理,如下图所示:
并不是所有的SQL都可以使用并行处理,一般只有对表或索引进行全部访问时才可以使用并行。数据库表默认是不打开并行访问,所以需要指定SQL并行的提示,如下所示:
select/*+parallel(a,4)*/*fromemployee;
并行的优点:
使用多进程处理,充分利用数据库主机资源(CPU,IO),提高性能。
并行的缺点:
1、单个会话占用大量资源,影响其它会话,所以只适合在主机负载低时期使用;
2、只能采用直接IO访问,不能利用缓存数据,所以执行前会触发将脏缓存数据写入磁盘操作。
1、并行处理在OLTP类系统中慎用,使用不当会导致一个会话把主机资源全部占用,而正常事务得不到及时响应,所以一般只是用于数据仓库平台。
2、一般对于百万级记录以下的小表采用并行访问性能并不能提高,反而可能会让性能更差。
今天面试,我简历上写了熟悉sql的性能优化,但是今天面试,一时想不起别的,就仅仅说出了一条,在这里再总结一些,完善自己的知识点。
我经常用的数据库是oracle,所以我的sql优化是程序员针对于oracle的。
总结,这个sql优化是针对程序员的,而不是针对dba的,主要就是第一,尽量防止模糊,明确指出,即用列名代替*,第二,在where语句上下工夫。第三多表查询和子查询,第四尽量使用绑定。
一、问题的提出
在应用系统开发初期,由于开发数据库数据比较少,对于查询SQL语句,复杂视图的的编写等体会不出SQL语句各种写法的性能优劣,但是如果将应用系统提交实际应用后,随着数据库中数据的增加,系统的响应速度就成为目前系统需要解决的最主要的问题之一。系统优化中一个很重要的方面就是SQL语句的优化。对于海量数据,劣质SQL语句和优质SQL语句之间的速度差别可以达到上百倍,可见对于一个系统不是简单地能实现其功能就可,而是要写出高质量的SQL语句,提高系统的可用性。
在多数情况下,Oracle使用索引来更快地遍历表,优化器主要根据定义的索引来提高性能。但是,如果在SQL语句的where子句中写的SQL代码不合理,就会造成优化器删去索引而使用全表扫描,一般就这种SQL语句就是所谓的劣质SQL语句。在编写SQL语句时我们应清楚优化器根据何种原则来删除索引,这有助于写出高性能的SQL语句。
二、SQL语句编写注意问题
下面就某些SQL语句的where子句编写中需要注意的问题作详细介绍。在这些where子句中,即使某些列存在索引,但是由于编写了劣质的SQL,系统在运行该SQL语句时也不能使用该索引,而同样使用全表扫描,这就造成了响应速度的极大降低。
1.操作符优化
(a)IN操作符
用IN写出来的SQL的优点是比较容易写及清晰易懂,这比较适合现代软件开发的风格。但是用IN的SQL性能总是比较低的,从Oracle执行的步骤来分析用IN的SQL与不用IN的SQL有以下区别:
ORACLE试图将其转换成多个表的连接,如果转换不成功则先执行IN里面的子查询,再查询外层的表记录,如果转换成功则直接采用多个表的连接方式查询。由此可见用IN的SQL至少多了一个转换的过程。一般的SQL都可以转换成功,但对于含有分组统计等方面的SQL就不能转换了。
推荐方案:在业务密集的SQL当中尽量不采用IN操作符,用EXISTS方案代替。
(b)NOTIN操作符
此操作是强列不推荐使用的,因为它不能应用表的索引。
推荐方案:用NOTEXISTS方案代替
(c)ISNULL或ISNOTNULL操作(判断字段是否为空)
判断字段是否为空一般是不会应用索引的,因为索引是不索引空值的。不能用null作索引,任何包含null值的列都将不会被包含在索引中。即使索引有多列这样的情况下,只要这些列中有一列含有null,该列就会从索引中排除。也就是说如果某列存在空值,即使对该列建索引也不会提高性能。任何在where子句中使用isnull或isnotnull的语句优化器是不允许使用索引的。
推荐方案:用其它相同功能的操作运算代替,如:aisnotnull改为a>0或a>’’等。不允许字段为空,而用一个缺省值代替空值,如申请中状态字段不允许为空,缺省为申请。
(d)>及<操作符(大于或小于操作符)
大于或小于操作符一般情况下是不用调整的,因为它有索引就会采用索引查找,但有的情况下可以对它进行优化,如一个表有100万记录,一个数值型字段A,30万记录的A=0,30万记录的A=1,39万记录的A=2,1万记录的A=3。那么执行A>2与A>=3的效果就有很大的区别了,因为A>2时ORACLE会先找出为2的记录索引再进行比较,而A>=3时ORACLE则直接找到=3的记录索引。
(e)LIKE操作符
LIKE操作符可以应用通配符查询,里面的通配符组合可能达到几乎是任意的查询,但是如果用得不好则会产生性能上的问题,如LIKE‘%5400%’这种查询不会引用索引,而LIKE‘X5400%’则会引用范围索引。
一个实际例子:用YW_YHJBQK表中营业编号后面的户标识号可来查询营业编号YY_BHLIKE‘%5400%’这个条件会产生全表扫描,如果改成YY_BHLIKE’X5400%’ORYY_BHLIKE’B5400%’则会利用YY_BH的索引进行两个范围的查询,性能肯定大大提高。
带通配符(%)的like语句:
同样以上面的例子来看这种情况。目前的需求是这样的,要求在职工表中查询名字中包含cliton的人。可以采用如下的查询SQL语句:
select*fromemployeewherelast_namelike'%cliton%';
这里由于通配符(%)在搜寻词首出现,所以Oracle系统不使用last_name的索引。在很多情况下可能无法避免这种情况,但是一定要心中有底,通配符如此使用会降低查询速度。然而当通配符出现在字符串其他位置时,优化器就能利用索引。在下面的查询中索引得到了使用:
select*fromemployeewherelast_namelike'c%';
(f)UNION操作符
UNION在进行表链接后会筛选掉重复的记录,所以在表链接后会对所产生的结果集进行排序运算,删除重复的记录再返回结果。实际大部分应用中是不会产生重复的记录,最常见的是过程表与历史表UNION。如:select*fromgc_dfysunionselect*fromls_jg_dfys这个SQL在运行时先取出两个表的结果,再用排序空间进行排序删除重复的记录,最后返回结果集,如果表数据量大的话可能会导致用磁盘进行排序。
推荐方案:采用UNIONALL操作符替代UNION,因为UNIONALL操作只是简单的将两个结果合并后就返回。
select*fromgc_dfysunionallselect*fromls_jg_dfys
(g)联接列
对于有联接的列,即使最后的联接值为一个静态值,优化器是不会使用索引的。我们一起来看一个例子,假定有一个职工表(employee),对于一个职工的姓和名分成两列存放(FIRST_NAME和LAST_NAME),现在要查询一个叫比尔.克林顿(BillCliton)的职工。
下面是一个采用联接查询的SQL语句:
select*fromemploysswherefirst_name||''||last_name='BeillCliton';
上面这条语句完全可以查询出是否有BillCliton这个员工,但是这里需要注意,系统优化器对基于last_name创建的索引没有使用。当采用下面这种SQL语句的编写,Oracle系统就可以采用基于last_name创建的索引。
***wherefirst_name='Beill'andlast_name='Cliton';
(h)Orderby语句
ORDERBY语句决定了Oracle如何将返回的查询结果排序。Orderby语句对要排序的列没有什么特别的限制,也可以将函数加入列中(象联接或者附加等)。任何在Orderby语句的非索引项或者有计算表达式都将降低查询速度。
仔细检查orderby语句以找出非索引项或者表达式,它们会降低性能。解决这个问题的办法就是重写orderby语句以使用索引,也可以为所使用的列建立另外一个索引,同时应绝对避免在orderby子句中使用表达式。
(i)NOT
我们在查询时经常在where子句使用一些逻辑表达式,如大于、小于、等于以及不等于等等,也可以使用and(与)、or(或)以及not(非)。NOT可用来对任何逻辑运算符号取反。下面是一个NOT子句的例子:
...wherenot(status='VALID')
如果要使用NOT,则应在取反的短语前面加上括号,并在短语前面加上NOT运算符。NOT运算符包含在另外一个逻辑运算符中,这就是不等于(<>)运算符。换句话说,即使不在查询where子句中显式地加入NOT词,NOT仍在运算符中,见下例:
...wherestatus<>'INVALID';
对这个查询,可以改写为不使用NOT:
select*fromemployeewheresalary<3000orsalary>3000;
虽然这两种查询的结果一样,但是第二种查询方案会比第一种查询方案更快些。第二种查询允许Oracle对salary列使用索引,而第一种查询则不能使用索引。
2.SQL书写的影响
(a)同一功能同一性能不同写法SQL的影响。
如一个SQL在A程序员写的为Select*fromzl_yhjbqk
B程序员写的为Select*fromdlyx.zl_yhjbqk(带表所有者的前缀)
C程序员写的为Select*fromDLYX.ZLYHJBQK(大写表名)
D程序员写的为Select*fromDLYX.ZLYHJBQK(中间多了空格)
(b)WHERE后面的条件顺序影响
WHERE子句后面的条件顺序对大数据量表的查询会产生直接的影响。如:Select*fromzl_yhjbqkwheredy_dj='1KV以下'andxh_bz=1Select*fromzl_yhjbqkwherexh_bz=1anddy_dj='1KV以下'以上两个SQL中dy_dj(电压等级)及xh_bz(销户标志)两个字段都没进行索引,所以执行的时候都是全表扫描,第一条SQL的dy_dj='1KV以下'条件在记录集内比率为99%,而xh_bz=1的比率只为0.5%,在进行第一条SQL的时候99%条记录都进行dy_dj及xh_bz的比较,而在进行第二条SQL的时候0.5%条记录都进行dy_dj及xh_bz的比较,以此可以得出第二条SQL的CPU占用率明显比第一条低。
(c)查询表顺序的影响
在FROM后面的表中的列表顺序会对SQL执行性能影响,在没有索引及ORACLE没有对表进行统计分析的情况下,ORACLE会按表出现的顺序进行链接,由此可见表的顺序不对时会产生十分耗服物器资源的数据交叉。(注:如果对表进行了统计分析,ORACLE会自动先进小表的链接,再进行大表的链接)
3.SQL语句索引的利用
(a)对条件字段的一些优化
采用函数处理的字段不能利用索引,如:
substr(hbs_bh,1,4)=’5400’,优化处理:hbs_bhlike‘5400%’
trunc(sk_rq)=trunc(sysdate),优化处理:sk_rq>=trunc(sysdate)andsk_rq 进行了显式或隐式的运算的字段不能进行索引,如:ss_df+20>50,优化处理:ss_df>30 ‘X’||hbs_bh>’X5400021452’,优化处理:hbs_bh>’5400021542’ sk_rq+5=sysdate,优化处理:sk_rq=sysdate-5 hbs_bh=5401002554,优化处理:hbs_bh=’5401002554’,注:此条件对hbs_bh进行隐式的to_number转换,因为hbs_bh字段是字符型。 条件内包括了多个本表的字段运算时不能进行索引,如: ys_df>cx_df,无法进行优化qc_bh||kh_bh=’5400250000’,优化处理:qc_bh=’5400’andkh_bh=’250000’ (1)选择最有效率的表名顺序(只在基于规则的优化器中有效): ORACLE的解析器按照从右到左的顺序处理FROM子句中的表名,FROM子句中写在最后的表(基础表drivingtable)将被最先处理,在FROM子句中包含多个表的情况下,你必须选择记录条数最少的表作为基础表。如果有3个以上的表连接查询,那就需要选择交叉表(intersectiontable)作为基础表,交叉表是指那个被其他表所引用的表. (2)WHERE子句中的连接顺序: ORACLE采用自下而上的顺序解析WHERE子句,根据这个原理,表之间的连接必须写在其他WHERE条件之前,那些可以过滤掉最大数量记录的条件必须写在WHERE子句的末尾. (3)SELECT子句中避免使用‘*‘: (4)减少访问数据库的次数: ORACLE在内部执行了许多工作:解析SQL语句,估算索引的利用率,绑定变量,读数据块等。 (5)在SQL*Plus,SQL*Forms和Pro*C中重新设置ARRAYSIZE参数,可以增加每次数据库访问的检索数据量,建议值为200。 使用DECODE函数可以避免重复扫描相同记录或重复连接相同的表. (7)整合简单,无关联的数据库访问: 如果你有几个简单的数据库查询语句,你可以把它们整合到一个查询中(即使它们之间没有关系)。 (8)删除重复记录: 最高效的删除重复记录方法(因为使用了ROWID)例子:DELETEFROMEMPEWHEREE.ROWID>(SELECTMIN(X.ROWID)FROMEMPXWHEREX.EMP_NO=E.EMP_NO)。 (9)用TRUNCATE替代DELETE: (10)尽量多使用COMMIT: 只要有可能,在程序中尽量多使用COMMIT,这样程序的性能得到提高,需求也会因为COMMIT所释放的资源而减少,COMMIT所释放的资源:a.回滚段上用于恢复数据的信息.b.被程序语句获得的锁c.redologbuffer中的空间d.ORACLE为管理上述3种资源中的内部花费 (11)用Where子句替换HAVING子句: (12)减少对表的查询: 在含有子查询的SQL语句中,要特别注意减少对表的查询.例子:SELECTTAB_NAMEFROMTABLESWHERE(TAB_NAME,DB_VER)=(SELECTTAB_NAME,DB_VERFROMTAB_COLUMNSWHEREVERSION=604) (13)通过内部函数提高SQL效率: 复杂的SQL往往牺牲了执行效率.能够掌握上面的运用函数解决问题的方法在实际工作中是非常有意义的。 (14)使用表的别名(Alias): (15)用EXISTS替代IN、用NOTEXISTS替代NOTIN: 在许多基于基础表的查询中,为了满足一个条件,往往需要对另一个表进行联接.在这种情况下,使用EXISTS(或NOTEXISTS)通常将提高查询的效率.在子查询中,NOTIN子句将执行一个内部的排序和合并.无论在哪种情况下,NOTIN都是最低效的(因为它对子查询中的表执行了一个全表遍历).为了避免使用NOTIN,我们可以把它改写成外连接(OuterJoins)或NOTEXISTS。例子:(高效)SELECT*FROMEMP(基础表)WHEREEMPNO>0ANDEXISTS(SELECT‘X'FROMDEPTWHEREDEPT.DEPTNO=EMP.DEPTNOANDLOC=‘MELB')(低效)SELECT*FROMEMP(基础表)WHEREEMPNO>0ANDDEPTNOIN(SELECTDEPTNOFROMDEPTWHERELOC=‘MELB') (16)识别'低效执行'的SQL语句: 虽然目前各种关于SQL优化的图形化工具层出不穷,但是写出自己的SQL工具来解决问题始终是一个最好的方法:SELECTEXECUTIONS,DISK_READS,BUFFER_GETS,ROUND((BUFFER_GETS-DISK_READS)/BUFFER_GETS,2)Hit_radio,ROUND(DISK_READS/EXECUTIONS,2)Reads_per_run,SQL_TEXTFROMV$SQLAREAWHEREEXECUTIONS>0ANDBUFFER_GETS>0AND(BUFFER_GETS-DISK_READS)/BUFFER_GETS<0.8ORDERBY4DESC; (17)用索引提高效率: (18)用EXISTS替换DISTINCT: 当提交一个包含一对多表信息(比如部门表和雇员表)的查询时,避免在SELECT子句中使用DISTINCT.一般可以考虑用EXIST替换,EXISTS使查询更为迅速,因为RDBMS核心模块将在子查询的条件一旦满足后,立刻返回结果.例子:(低效):SELECTDISTINCTDEPT_NO,DEPT_NAMEFROMDEPTD,EMPEWHERED.DEPT_NO=E.DEPT_NO(高效):SELECTDEPT_NO,DEPT_NAMEFROMDEPTDWHEREEXISTS(SELECT‘X'FROMEMPEWHEREE.DEPT_NO=D.DEPT_NO); (19)sql语句用大写的;因为oracle总是先解析sql语句,把小写的字母转换成大写的再执行。 (20)在java代码中尽量少用连接符“+”连接字符串! (21)避免在索引列上使用NOT,通常我们要避免在索引列上使用NOT,NOT会产生在和在索引列上使用函数相同的影响.当ORACLE”遇到”NOT,他就会停止使用索引转而执行全表扫描。 (22)避免在索引列上使用计算WHERE子句中,如果索引列是函数的一部分.优化器将不使用索引而使用全表扫描.举例:低效:SELECT…FROMDEPTWHERESAL*12>25000;高效:SELECT…FROMDEPTWHERESAL>25000/12; (23)用>=替代>高效:SELECT*FROMEMPWHEREDEPTNO>=4低效:SELECT*FROMEMPWHEREDEPTNO>3两者的区别在于,前者DBMS将直接跳到第一个DEPT等于4的记录而后者将首先定位到DEPTNO=3的记录并且向前扫描到第一个DEPT大于3的记录。 (24)用UNION替换OR(适用于索引列) 通常情况下,用UNION替换WHERE子句中的OR将会起到较好的效果.对索引列使用OR将造成全表扫描.注意,以上规则只针对多个索引列有效.如果有column没有被索引,查询效率可能会因为你没有选择OR而降低.在下面的例子中,LOC_ID和REGION上都建有索引.高效:SELECTLOC_ID,LOC_DESC,REGIONFROMLOCATIONWHERELOC_ID=10UNIONSELECTLOC_ID,LOC_DESC,REGIONFROMLOCATIONWHEREREGION=“MELBOURNE”低效:SELECTLOC_ID,LOC_DESC,REGIONFROMLOCATIONWHERELOC_ID=10ORREGION=“MELBOURNE”如果你坚持要用OR,那就需要返回记录最少的索引列写在最前面. (25)用IN来替换OR 这是一条简单易记的规则,但是实际的执行效果还须检验,在ORACLE8i下,两者的执行路径似乎是相同的.低效:SELECT….FROMLOCATIONWHERELOC_ID=10ORLOC_ID=20ORLOC_ID=30高效SELECT…FROMLOCATIONWHERELOC_ININ(10,20,30); (26)避免在索引列上使用ISNULL和ISNOTNULL 避免在索引中使用任何可以为空的列,ORACLE将无法使用该索引.对于单列索引,如果列包含空值,索引中将不存在此记录.对于复合索引,如果每个列都为空,索引中同样不存在此记录.如果至少有一个列不为空,则记录存在于索引中.举例:如果唯一性索引建立在表的A列和B列上,并且表中存在一条记录的A,B值为(123,null),ORACLE将不接受下一条具有相同A,B值(123,null)的记录(插入).然而如果所有的索引列都为空,ORACLE将认为整个键值为空而空不等于空.因此你可以插入1000条具有相同键值的记录,当然它们都是空!因为空值不存在于索引列中,所以WHERE子句中对索引列进行空值比较将使ORACLE停用该索引.低效:(索引失效)SELECT…FROMDEPARTMENTWHEREDEPT_CODEISNOTNULL;高效:(索引有效)SELECT…FROMDEPARTMENTWHEREDEPT_CODE>=0; (27)总是使用索引的第一个列: 如果索引是建立在多个列上,只有在它的第一个列(leadingcolumn)被where子句引用时,优化器才会选择使用该索引.这也是一条简单而重要的规则,当仅引用索引的第二个列时,优化器使用了全表扫描而忽略了索引。 (28)用UNION-ALL替换UNION(如果有可能的话): 当SQL语句需要UNION两个查询结果集合时,这两个结果集合会以UNION-ALL的方式被合并,然后在输出最终结果前进行排序.如果用UNIONALL替代UNION,这样排序就不是必要了.效率就会因此得到提高.需要注意的是,UNIONALL将重复输出两个结果集合中相同记录.因此各位还是要从业务需求分析使用UNIONALL的可行性.UNION将对结果集合排序,这个操作会使用到SORT_AREA_SIZE这块内存.对于这块内存的优化也是相当重要的.下面的SQL可以用来查询排序的消耗量低效:SELECTACCT_NUM,BALANCE_AMTFROMDEBIT_TRANSACTIONSWHERETRAN_DATE='31-DEC-95'UNIONSELECTACCT_NUM,BALANCE_AMTFROMDEBIT_TRANSACTIONSWHERETRAN_DATE='31-DEC-95'高效:SELECTACCT_NUM,BALANCE_AMTFROMDEBIT_TRANSACTIONSWHERETRAN_DATE='31-DEC-95'UNIONALLSELECTACCT_NUM,BALANCE_AMTFROMDEBIT_TRANSACTIONSWHERETRAN_DATE='31-DEC-95' (29)用WHERE替代ORDERBY: ORDERBY子句只在两种严格的条件下使用索引.ORDERBY中所有的列必须包含在相同的索引中并保持在索引中的排列顺序.ORDERBY中所有的列必须定义为非空.WHERE子句使用的索引和ORDERBY子句中所使用的索引不能并列.例如:表DEPT包含以下列:DEPT_CODEPKNOTNULLDEPT_DESCNOTNULLDEPT_TYPENULL低效:(索引不被使用)SELECTDEPT_CODEFROMDEPTORDERBYDEPT_TYPE高效:(使用索引)SELECTDEPT_CODEFROMDEPTWHEREDEPT_TYPE>0 (30)避免改变索引列的类型: 当比较不同数据类型的数据时,ORACLE自动对列进行简单的类型转换.假设EMPNO是一个数值类型的索引列.SELECT…FROMEMPWHEREEMPNO=‘123'实际上,经过ORACLE类型转换,语句转化为:SELECT…FROMEMPWHEREEMPNO=TO_NUMBER(‘123')幸运的是,类型转换没有发生在索引列上,索引的用途没有被改变.现在,假设EMP_TYPE是一个字符类型的索引列.SELECT…FROMEMPWHEREEMP_TYPE=123这个语句被ORACLE转换为:SELECT…FROMEMPWHERETO_NUMBER(EMP_TYPE)=123因为内部发生的类型转换,这个索引将不会被用到!为了避免ORACLE对你的SQL进行隐式的类型转换,最好把类型转换用显式表现出来.注意当字符和数值比较时,ORACLE会优先转换数值类型到字符类型。 分析selectemp_nameformemployeewheresalary>3000在此语句中若salary是Float类型的,则优化器对其进行优化为Convert(float,3000),因为3000是个整数,我们应在编程时使用3000.0而不要等运行时让DBMS进行转化。同样字符和整型数据的转换。 (31)需要当心的WHERE子句: 某些SELECT语句中的WHERE子句不使用索引.这里有一些例子.在下面的例子里,(1)‘!='将不使用索引.记住,索引只能告诉你什么存在于表中,而不能告诉你什么不存在于表中.(2)‘||'是字符连接函数.就象其他函数那样,停用了索引.(3)‘+'是数学函数.就象其他数学函数那样,停用了索引.(4)相同的索引列不能互相比较,这将会启用全表扫描. (32)a.如果检索数据量超过30%的表中记录数.使用索引将没有显著的效率提高.b.在特定情况下,使用索引也许会比全表扫描慢,但这是同一个数量级上的区别.而通常情况下,使用索引比全表扫描要块几倍乃至几千倍! (33)避免使用耗费资源的操作: 带有DISTINCT,UNION,MINUS,INTERSECT,ORDERBY的SQL语句会启动SQL引擎执行耗费资源的排序(SORT)功能.DISTINCT需要一次排序操作,而其他的至少需要执行两次排序.通常,带有UNION,MINUS,INTERSECT的SQL语句都可以用其他方式重写.如果你的数据库的SORT_AREA_SIZE调配得好,使用UNION,MINUS,INTERSECT也是可以考虑的,毕竟它们的可读性很强。 (34)优化GROUPBY: 提高GROUPBY语句的效率,可以通过将不需要的记录在GROUPBY之前过滤掉.下面两个查询返回相同结果但第二个明显就快了许多.低效:SELECTJOB,AVG(SAL)FROMEMPGROUPbyJOBHAVINGJOB=‘PRESIDENT'ORJOB=‘MANAGER'高效:SELECTJOB,AVG(SAL)FROMEMPWHEREJOB=‘PRESIDENT'ORJOB=‘MANAGER'GROUPbyJOB 一、索引1、创建索引:(1).ALTERTABLEALTERTABLE用来创建普通索引、UNIQUE索引或PRIMARYKEY索引。ALTERTABLEtable_nameADDINDEXindex_name(column_list)ALTERTABLEtable_nameADDUNIQUE(column_list)ALTERTABLEtable_nameADDPRIMARYKEY(column_list)(2)、CREATEINDEXCREATEINDEX可对表增加普通索引或UNIQUE索引。CREATEINDEXindex_nameONtable_name(column_list)CREATEUNIQUEINDEXindex_nameONtable_name(column_list)2、查看索引mysql>showindexfromtblname;mysql>showkeysfromtblname;3、删除索引可利用ALTERTABLE或DROPINDEX语句来删除索引。类似于CREATEINDEX语句,DROPINDEX可以在ALTERTABLE内部作为一条语句处理,语法如下。DROPINDEXindex_nameONtalbe_nameALTERTABLEtable_nameDROPINDEXindex_nameALTERTABLEtable_nameDROPPRIMARYKEY 四、索引的类型:1、B-Tree索引2、hash索引具体的参考还是一) 八、慢查询日志++++需要开启++++通过慢日志查询可以知道哪些SQL语句执行效率低下,那些sql语句使用的频率高等对MySQL查询语句的监控、分析、优化是MySQL优化非常重要的一步。开启慢查询日志后,由于日志记录操作,在一定程度上会占用CPU资源影响mysql的性能,但是可以阶段性开启来定位性能瓶颈。