数据库SQL性能调优

      系统优化中一个很重要的方面就是SQL语句的优化。对于海量数据,劣质SQL语句和优质SQL语句之间的速度差别可以达到上百倍;可见,对于一个系统不是简单地能实现其功能就可,而是要写出高质量的SQL语句,提高系统的可用性。

      在多数情况下,数据库(Oracle/DB2/MySql等)使用索引来更快地遍历表,优化器/工具主要根据定义的索引来提高性能。但是,如果在SQL语句的where子句中写的SQL代码不合理,就会造成优化器不使用索引而使用全表扫描(劣质SQL)。一般就这种SQL语句就是所谓的劣质SQL语句。在编写SQL语句时我们应清楚优化器根据何种原则来删除索引,这有助于写出高性能的SQL语句。

(一)、数据结构及索引利弊

1、通常,尤其是大型数据库(Oracle/DB2等)的数据结构都是采用平衡树(Balanced-tree)结构。如Oracle数据库采用复杂的自平衡结构B-tree

2、B-tree亦称B-树/B树,它的变体有B+-tree/ B*-tree (B~Tree)等。B-tree一种多路搜索树,不仅限于二叉;它区别于二叉查找树(Binary Search Tree)/平衡二叉查找树(Balanced Binary Search Tree)/红黑树 (Red-Black Tree ),后三种是典型的二叉查找树。

3、索引利弊:     

3.1、良好的索引选择性高的索引。索引的选择性可以帮助基于成本的优化器来决定执行路径。索引的选择性越高,针对每个不同值平均返回的行数也越少。对于组合索引,在索引中添加额外的列不会显著改善选择性,额外列增加的成本可能会超出收益           

3.2、滥加索引的弊端:                         

3.2.1、存储在磁盘空间中,需要定期维护;                         

3.2.2、每当增减记录或则索引列被修改时,索引也会被修改;这样一个数据库增删改操作会导致额外的磁盘I/O访问(4-5次);                     3.2.3、索引定期的重构也是必要的(Oracle语法):ALTER  INDEX <INDEX_NAME> REBUILD TABLESPACE <TABLESPACE_NAME>;

3.3、扩展

3.3.1、集群因子:它记录在扫描索引时需要读取的数据块数量。如果使用的索引具有较大的集群因子,就必须访问更多的表数据块才可以获得每个索引块中对应的数据行(因为邻近行位于不同的数据块中)。如果集群因子接近于表中的数据块数量,就表示索引对应数据行的排序情况良好;反之,如果集群因子接近于表中的数据行数量,就表示索引对应的数据块排序情况不佳。表中数据的集群因子,可以用来提高范围扫描类型操作语句的性能。通过决定在语句中列是如何使用的,对这些列进行索引可以带来很大益处。

(二)、合理利用索引

    4.1、null列:创建索引时,索引列中有某列包含null值,这样的列将不会包含在索引中;亦即该索引不会对该列起作用。

    4.2、where条件中带is null/is not null的子句,优化器不会走索引;因为数据库中没有值等于NULL,甚至NULL 也不等于NULL;该列在索引中不会被创建条目

    4.3、where条件的谓词列不能带有修饰,如调用了函数、进行了运算、连接了其它列或字符等;否则索引不起作用(除非使用基于函数的索引,不过这样的索引有很大局限性),需单独放在条件表达式一侧。

    4.4、通配符%出现在词首时,索引无效,如:xx like '%hello%'; 反之xx like 'hello%' 则是索引有效的。

    4.5、Order by子句中非索引列,尤其是含运算表达式或函数的列,会降低查询速度。可以对非索引列添加索引或者重写子句,用索引列排序;同时避免函数表达式在子句中的运用;

    4.6、不等于运算符(<>、!=)索引只能用于查找表中已存在的数据;每当在WHERE子句中使用不等于运算符时,其中对应列上的索引都将无法使用。不过这并不等于性能被降低,而是优化器基于(性能)成本的考量做的决定:在WHERE 子句中对一些值使用索引,而对部分值(不等式等)不使用因为通常大型数据库分析表的同时都收集了表中数据分布的相关统计信息

4.6.1、不等式的逻辑运算本身需返回绝大多数数据行,因此仍会产生一次全表扫描;    4.6.2、在应用程序开发、测试期间,应使用有代表性的测试数据,从而可以模拟产品环境中实际的数据值分布情况。    4.6.3、Oracle创建带分析索引4.6.3.1、后跟COMPUTE STATISTICS子句:CREATE INDEX index_name ON table_name(column_name)COMPUTE STATISTICS;4.6.3.2、10g不立即起作用,需执行:ANALYZE TABLE table_name(column_name) COMPUTE STATISTICS;

二、改进sql语句,提高SQL性能

了解数据库特性,养成良好的书写sql习惯。一个优质的sql可以很大程度提高sql检索效率,而且有些场景下这些问题往往隐藏的很深,难以发现,需要在平时的工作中逐步摸索、慢慢养成。

1、比较不匹配的数据类型   

1.1、当Where条件中谓词列与比较值的数据类型不同时,数据库查询优化器(如Oracle)不但不会报错,反而会进行隐式数据转换,从而去匹配比较值的数据类型。比如:acco列为VARCHAR2类型,执行子句where acco99011 会转换成 where to_number(acco) = 99011。自然索引被抑制,产生全表扫描。

1.2、改进做法:传递对应数据类型比较值以匹配谓词列(推崇);结合整体业务需要,考虑重新定义合适的谓词列数据类型以匹配比较值。

2、select子句避免用*,否则会查询数据字典带来额外开销;sql语句用大写;尽量用Where子句替换Having子句,因为Having子句是在检索出来的结果集上再次过滤,过程中需要排序、总计,产生额外开销;多表查询时给表起别名可避免列歧义减少解析时间。

3、(NOT)EXISTS代替(NOT)IN: 

有时候查询会包含子查询,用(NOT)IN相比用(NOT)EXISTS,查询的数据范围更宽;因为(NOT)EXISTS子句中可以带上主查询的关联条件,而(NOT) IN没有。如: 高效:SELECT * FROM  EMP (基础表)  WHERE  EMPNO > 0  AND  EXISTS (SELECT ‘X'  FROM DEPT  WHERE  DEPT.DEPTNO = EMP.DEPTNO  AND  LOC = ‘MELB'); 
低效:
SELECT  * FROM  EMP (基础表)  WHERE  EMPNO > 0  AND  DEPTNO IN(SELECT DEPTNO  FROM  DEPT  WHERE  LOC = ‘MELB');

4、用EXISTS代替DISTINCT去重: 

在多表连接,尤其是一对多的表连接中,如果只需要‘一’表的数据,可以采用EXISTS子句替换主查询的多表连接,这样避免了主查询中出现重复的数据。因为DISTINCT会在查询出来的结果集基础上进行排序、去重,增加了额外的工作。如:低效:SELECT  DISTINCT  D.DEPT_NO,D.DEPT_NAME  FROM  DEPT D , EMP E WHERE D.DEPT_NO = E.DEPT_NO;
高效:SELECT  DEPT_NO,DEPT_NAME  FROM  DEPT D  WHERE  EXISTS ( SELECT ‘X'FROM  EMP E  WHERE E.DEPT_NO = D.DEPT_NO);

5、高效的删除重复记录sql
 DELETE  FROM  EMP E  WHERE  E.ROWID > (SELECT MIN(X.ROWID) FROM  EMP X  WHERE  X.EMP_NO = E.EMP_NO); 

6、用TRUNCATE替代DELETE以释放空间DELETE:DML操作,数据存放在回滚段(rollback segments)中,执行COMMIT则真正删除,反之回滚。TRUNCATE:DDL操作,会彻底删除数据,不存回滚段;资源调用少、执行快。彻底不用的表可以采用此命令

7、在查询数据无差别的情况下,采用group by比distinct快,不论查询列是否添加索引。

三、sql语句优化--深层次

1、sql优化中,一个重要的基本原则就是“永远用小结果集驱动大的结果集”。

为什么?因为驱动结果集越大,意味着需要循环的次数越多,也就是说在被驱动结果集上面所需要执行的查询检索次数会越多。当然,此优化的前提条件是通过Join条件对各个表的每次访问的资源消耗差别不是太大。如果访问存在较大的差别的时候(一般都是因为索引的区别),我们就不能简单的通过结果集的大小来判断需要Join语句的驱动顺序,而是要通过比较循环次数和每次循环所需要的消耗的乘积的大小来得到如何驱动更优化。



1 性能优化 1.1 避免频繁 commit,尤其是把 commit 写在循环体中每次循环都进行commit。 1.2 使用绑定变量,避免常量的直接引用。 示例:以下书写不符合本规范。 INSERT INTO sm_users (user_id, user_name, created_by, creation_date) VALUES (1, 'Tang', -1, SYSDATE); 建议用如下方式操作: DECLARE v_user_id sm_users.user_id%TYPE; v_user_name sm_users_user_name%TYPE; v_created_by sm_users.created_by%TYPE; v_creation_date sm_users.creation_date%TYPE; BEGIN ... INSERT INTO sm_users (user_id, user_name, created_by, creation_date) VALUES (v_user_id, v_user_name, v_created_by, v_creation_date); END; 1.3 Operator 的使用规范  IN  比较容易写及清晰易懂  但效能是比较低的  ORACLE试图将其转换成多个表的连接,如果转换不成功则先执行IN里面的子查询,再查询外层的表记录,如果转换成功则直接采用多个表的连接方式查询。由此可见用IN的SQL至少多了一个转换的过程。一般的SQL都可以转换成功,但对于含有分组统计等方面的SQL就不能转换了。  NOT IN  此操作是强列推荐不使用的,因为不能应用表的索引。  推荐方案:用NOT EXISTS 或(Outer-Join+判断为空)方案代替 例如: SELECT deptno FROM dept WHERE deptno NOT IN(SELECT deptno FROM emp) 建议写成: SELECT deptno FROM dept, emp WHERE dept.deptno = emp.deptno(+) AND emp.deptno IS NULL  <>  永远不会用到索引的  推荐方案:用其它相同功能的操作运算代替,如: a<>0 改为 a>0 or a<0 a<>’’ 改为 a>’’  IS NULL 或IS NOT NULL  一般是不会应用索引的,因为B-tree索引是不索引空值的。  推荐方案:用其它相同功能的操作运算代替,如: a is not null 改为 a>0 或a>’’  不允许字段为空,而用一个default代替空值,如业扩申请中状态区位不允许为空, default为申请。  > 及 <  有索引就会采用索引查找  但有的情况下可以对它进行优化  如一个表有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的记录索引。  LIKE  LIKE可以应用通配符查询,里面的通配符组合可能达到几乎是任意的查询,但是如果用得不好则会产生性能上的问题,如LIKE ‘%5400%’ 这种查询不会引用索引,而LIKE ‘X5400%’则会引用范围索引。性能肯定大大提高。  UNION  SQL在运行时先取出数个查询的结果,再用排序空间进行排序删除重复的记录,最后返回结果集,如果表数据量大的话可能会导致用磁盘进行排序。  实际大部分应用中是不会产生重复的记录,推荐采用UNION ALL操作符替代UNION,因为UNION ALL操作只是简单的将两个结果合并后就返回。  Exists 示例:当有 A、B 两个结果集,当结果集 B 很大时,A 较小时,适用 exists,如: SELECT * FROM a WHERE EXISTS(SELECT 1 FROM b WHERE a.COLUMN = b.COLUMN); 当结果集 A 很大时,B 很小时,适用 in,如: SELECT * FROM a WHERE a.COLUMN IN(SELECT b.COLUMN FROM b) 1.4 SQL书写的影响  同一功能同一性能不同写法SQL的影响  Select * from zl_yhjbqk  Select * from dlyx.zl_yhjbqk(带表所有者的前缀)  Select * from DLYX.ZL_YHJBQK(大写表名)  Select * from DLYX.ZL_YHJBQK(中间多了空格)  以上四个SQL在ORACLE分析整理之后产生的结果及执行的时间是一样的,但是从ORACLE共享内存SGA的原理,可以得出ORACLE对每个SQL 都会对其进行一次分析,并且占用共享内存,如果将SQL的字符串及格式写得完全相同则ORACLE只会分析一次,共享内存也只会留下一次的分析结果,这不仅可以减少分析SQL的时间,而且可以减少共享内存重复的信息,ORACLE也可以准确统计SQL的执行频率。  WHERE后面的条件顺序影响  Select * from zl_yhjbqk where dy_dj = '1KV以下' and xh_bz=1  Select * from zl_yhjbqk where xh_bz=1 and dy_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占用率明显比第一条低。  查询表顺序的影响  在FROM后面的表中的列表顺序会对SQL执行性能影响,在没有索引及ORACLE没有对表进行统计分析的情况下ORACLE会按表出现的顺序进行链接,由此因为表的顺序不对会产生十分耗服务器资源的数据交叉。(注:如果对表进行了统计分析,ORACLE会自动先进小表的链接,再进行大表的链接)  对条件字段的一些优化  采用函数处理的字段不能利用索引,如: substr(hbs_bh,1,4)=’5400’,优化处理:hbs_bh like ‘5400%’ trunc(sk_rq)=trunc(sysdate), 优化处理: sk_rq>=trunc(sysdate) and sk_rq<trunc(sysdate+1)  进行了显式或隐式的运算的字段不能进行索引,如: 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’ and kh_bh=’250000’  HINT  是在ORACLE产生的SQL分析执行路径不满意的情况下要用到的。它可以对SQL进行以下方面的提示  目标方面的提示:  COST(按成本优化)  RULE(按规则优化)  CHOOSE(缺省)(ORACLE自动选择成本或规则进行优化)  ALL_ROWS(所有的行尽快返回)  FIRST_ROWS(第一行资料尽快返回)  执行方法的提示:  USE_NL(使用NESTED LOOPS方式联合)  USE_MERGE(使用MERGE JOIN方式联合)  USE_HASH(使用HASH JOIN方式联合)  索引提示:  INDEX(TABLE INDEX)(使用提示的表索引进行查询)  其它高级提示(如并行处理等等) 1.5 索引的规则: 建立索引常用的原则如下: 1. 表的主键、外键必须有索引 2. 数据量超过 1000 行的表应该有索引 3. 经常与其它表进行连接的表,在边接字段上应建立索引 4. 经常出现在 where 子句中的字段且过滤性极强的,特别是大表的字段,应该建立索引 5. 索引字段,尽量避免值为 null 6. 复合索引的建立需要仔细分析;尽量考虑用单字段索引代替:  正确选择复合索引中的第一个字段,一般是选择性较好的且在 where 子句中常的字段上。  复合索引的几个字段是否经常同时以and方式出现在where子句中?单字段查询是否极少甚至没有?如果是,则可以建立复合索引;否则考虑单字段索引。  如果复合索引中包含的字段经常单独出现在 where 子句中,则分解为多个单字段索引。  如果复合索引所包含的字段超过 3 个,那么仔细考虑其必要性,考虑减少复合的字段。  如果既有单字段索引,又有这几个字段上的复合索引,一般可以删除复合索引; 7. 频繁 DDL 的表,不要建立太多的索引 8. 删除无用的索引,避免对执行计划造成负面影响 9. 让 SQL 语句用上合理的索引,合理让 SQL 语句使用索引的原则如下:  首先,看是否用上了索引,对于该使用索引而没有用上索引的 SQL 语句,应该想办法用上索引。  其次,看是否用上正确的索引了,特别复杂的 SQL 语句,当其中 where 子句包含多个带有索引的字段时,更应该注意索引的选择是否合理。错误的索引不仅不会带来性能的提高,相反往往导致性能的降低。  针对如何用上合理的索引,以 Oracle 数据中的例子进行说明:  任何对列的操作都可能导致全表扫描,这里所谓的操作包括数据库函数、计算表达式等等,查询时要尽可能将操作移至等式的右边,甚至去掉函数。  避免不必要的类型转换,要了解“隐藏”的类型转换。  增加查询的范围,限制全范围的搜索。  索引选择性低,但资料分布差异很大时,仍然可以利用索引提高效率。  Oracle 优化器无法用上合理索引的情况下,利用 hint 强制指定索引。  使用复合索引且第一个索引字段没有出现在 where 中时,建议使用 hint 强制。 1.6 索引使用优化  建立Plan_Table CREATE TABLE PLAN_TABLE ( STATEMENT_ID VARCHAR2(30), TIMESTAMP DATE, REMARKS VARCHAR2(80), OPERATION VARCHAR2(30), OPTIONS VARCHAR2(30), OBJECT_NODE VARCHAR2(128), OBJECT_OWNER VARCHAR2(30), OBJECT_NAME VARCHAR2(30), OBJECT_INSTANCE NUMBER(38), OBJECT_TYPE VARCHAR2(30), OPTIMIZER VARCHAR2(255), SEARCH_COLUMNS NUMBER(38), ID NUMBER(38), PARENT_ID NUMBER(38), POSITION NUMBER(38), OTHER LONG )  Syntax 说明: explain plan set statement_id = user_define for select ... 将结果显示 SELECT LPAD(' ', 2 *(LEVEL - 1)) || operation op, options, object_name, POSITION FROM plan_table START WITH ID = 0 AND STATEMENT_ID = user_define CONNECT BY PRIOR ID = parent_id AND STATEMENT_ID = user_define  示例 如要测试下面SQL: SELECT c.short, a.cday, a.card_no, a.qty FROM sales.stockiohis a, sales.product_info b, sales.vendor c WHERE a.card_no = b.card_no AND b.vendorid = c.vendorid AND a.produce_no = '2007090001' AND a.CATEGORY = '10' AND a.iotype = '1' 新增文件:例 d:\mydoc\plan.sql '0001'为user_define为使用者自定义编号 EXPLAIN PLAN SET STATEMENT_ID = '0001' FOR SELECT 'X' FROM sales.stockiohis a ,sales.product_info b ,sales.vendor c WHERE a.card_no = b.card_no AND b.vendorid = c.vendorid AND a.produce_no = '2007090001' AND a.CATEGORY = '10' AND a.iotype = '1' / SET arraysize 1 SET line 100 COLUMN op format a40 COLUMN object_name format a20 COLUMN options format a20 SELECT LPAD(' ', 2 *(LEVEL - 1)) || operation op, options, object_name, POSITION FROM plan_table START WITH ID = 0 AND STATEMENT_ID = '0001' CONNECT BY PRIOR ID = parent_id AND STATEMENT_ID = '0001' / DELETE FROM plan_table WHERE STATEMENT_ID = '0001' / COMMIT / 结果 1.7 避免不必要的排序 说明:对查询结果进行排序会大大的降低系统的性能,group与union都会对数据作排序,要耗费较多的内存,视状况用union all既可,不然有时数据太大又要进行union的排序,会导致Oracle数据库SORT_AREA_SIZE不足发生系统错误。 1.8 对于数字型的Primary Key,建议用序列 sequence 产生。 说明:除非是单据的单号,要求必须是唯一,并且依据流水号不可以跳号,不然在大量交易的表格中,不在乎跳耗时,要取得唯一的Primary Key 建议使用Oracle Sequence这样速度会较快,而且不会有锁定(Lock)的问题。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值